Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
af5fb16f2a
commit
ae96e65ee2
|
@ -475,7 +475,6 @@ Style/MixinUsage:
|
|||
Style/MultilineIfModifier:
|
||||
Exclude:
|
||||
- 'app/helpers/snippets_helper.rb'
|
||||
- 'app/models/project_wiki.rb'
|
||||
- 'app/services/ci/process_pipeline_service.rb'
|
||||
- 'lib/api/commit_statuses.rb'
|
||||
|
||||
|
|
|
@ -39,7 +39,11 @@ export const diffCompareDropdownTargetVersions = (state, getters) => {
|
|||
...v,
|
||||
};
|
||||
};
|
||||
return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion, headVersion];
|
||||
|
||||
if (gon.features?.diffCompareWithHead) {
|
||||
return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion, headVersion];
|
||||
}
|
||||
return [...state.mergeRequestDiffs.slice(1).map(formatVersion), baseVersion];
|
||||
};
|
||||
|
||||
export const diffCompareDropdownSourceVersions = (state, getters) => {
|
||||
|
|
|
@ -34,10 +34,21 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
defaultAwards: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
groupedDefaultAwards() {
|
||||
return this.defaultAwards.reduce((obj, key) => Object.assign(obj, { [key]: [] }), {});
|
||||
},
|
||||
groupedAwards() {
|
||||
const { thumbsup, thumbsdown, ...rest } = groupBy(this.awards, x => x.name);
|
||||
const { thumbsup, thumbsdown, ...rest } = {
|
||||
...this.groupedDefaultAwards,
|
||||
...groupBy(this.awards, x => x.name),
|
||||
};
|
||||
|
||||
return [
|
||||
...(thumbsup ? [this.createAwardList('thumbsup', thumbsup)] : []),
|
||||
|
@ -73,6 +84,10 @@ export default {
|
|||
};
|
||||
},
|
||||
getAwardListTitle(awardsList) {
|
||||
if (!awardsList.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const hasReactionByCurrentUser = this.hasReactionByCurrentUser(awardsList);
|
||||
const TOOLTIP_NAME_COUNT = hasReactionByCurrentUser ? 9 : 10;
|
||||
let awardList = awardsList;
|
||||
|
|
|
@ -307,7 +307,7 @@
|
|||
}
|
||||
|
||||
.label-name {
|
||||
width: 150px;
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.scoped-label-wrapper,
|
||||
|
|
|
@ -26,6 +26,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:code_navigation, @project)
|
||||
push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true)
|
||||
push_frontend_feature_flag(:merge_ref_head_comments, @project)
|
||||
push_frontend_feature_flag(:diff_compare_with_head, @project)
|
||||
end
|
||||
|
||||
before_action do
|
||||
|
|
|
@ -129,7 +129,7 @@ class Blob < SimpleDelegator
|
|||
|
||||
def external_storage_error?
|
||||
if external_storage == :lfs
|
||||
!project&.lfs_enabled?
|
||||
!repository.lfs_enabled?
|
||||
else
|
||||
false
|
||||
end
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
# needs any special behavior.
|
||||
module HasRepository
|
||||
extend ActiveSupport::Concern
|
||||
include AfterCommitQueue
|
||||
include Referable
|
||||
include Gitlab::ShellAdapter
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module HasWiki
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
validate :check_wiki_path_conflict
|
||||
end
|
||||
|
||||
def create_wiki
|
||||
wiki.wiki
|
||||
true
|
||||
rescue Wiki::CouldNotCreateWikiError
|
||||
errors.add(:base, _('Failed to create wiki'))
|
||||
false
|
||||
end
|
||||
|
||||
def wiki
|
||||
strong_memoize(:wiki) do
|
||||
Wiki.for_container(self, self.owner)
|
||||
end
|
||||
end
|
||||
|
||||
def wiki_repository_exists?
|
||||
wiki.repository_exists?
|
||||
end
|
||||
|
||||
def after_wiki_activity
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_wiki_path_conflict
|
||||
return if path.blank?
|
||||
|
||||
path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki"
|
||||
|
||||
if Project.in_namespace(parent_id).where(path: path_to_check).exists? ||
|
||||
GroupsFinder.new(nil, parent: parent_id).execute.where(path: path_to_check).exists?
|
||||
errors.add(:name, _('has already been taken'))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -26,7 +26,7 @@ module RedisCacheable
|
|||
end
|
||||
|
||||
def cache_attributes(values)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
Gitlab::Redis::Cache.with do |redis|
|
||||
redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME)
|
||||
end
|
||||
|
||||
|
@ -41,7 +41,7 @@ module RedisCacheable
|
|||
|
||||
def cached_attributes
|
||||
strong_memoize(:cached_attributes) do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
Gitlab::Redis::Cache.with do |redis|
|
||||
data = redis.get(cache_attribute_key)
|
||||
JSON.parse(data, symbolize_names: true) if data
|
||||
end
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Storage
|
||||
module LegacyProjectWiki
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def disk_path
|
||||
project.disk_path + '.wiki'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,6 +15,7 @@ class Group < Namespace
|
|||
include WithUploads
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include GroupAPICompatibility
|
||||
include HasWiki
|
||||
|
||||
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GroupWiki < Wiki
|
||||
alias_method :group, :container
|
||||
|
||||
override :storage
|
||||
def storage
|
||||
@storage ||= Storage::Hashed.new(container, prefix: Storage::Hashed::GROUP_REPOSITORY_PATH_PREFIX)
|
||||
end
|
||||
|
||||
override :repository_storage
|
||||
def repository_storage
|
||||
# TODO: Add table to track storage
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/207865
|
||||
'default'
|
||||
end
|
||||
|
||||
override :hashed_storage?
|
||||
def hashed_storage?
|
||||
true
|
||||
end
|
||||
|
||||
override :disk_path
|
||||
def disk_path(*args, &block)
|
||||
storage.disk_path + '.wiki'
|
||||
end
|
||||
end
|
|
@ -2,4 +2,8 @@
|
|||
|
||||
class PersonalSnippet < Snippet
|
||||
include WithUploads
|
||||
|
||||
def skip_project_check?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
require 'carrierwave/orm/activerecord'
|
||||
|
||||
class Project < ApplicationRecord
|
||||
extend ::Gitlab::Utils::Override
|
||||
include Gitlab::ConfigHelper
|
||||
include Gitlab::VisibilityLevel
|
||||
include AccessRequestable
|
||||
|
@ -18,6 +19,7 @@ class Project < ApplicationRecord
|
|||
include SelectForProjectAuthorization
|
||||
include Presentable
|
||||
include HasRepository
|
||||
include HasWiki
|
||||
include Routable
|
||||
include GroupDescendant
|
||||
include Gitlab::SQL::Pattern
|
||||
|
@ -386,7 +388,6 @@ class Project < ApplicationRecord
|
|||
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
|
||||
validate :visibility_level_allowed_by_group, if: :should_validate_visibility_level?
|
||||
validate :visibility_level_allowed_as_fork, if: :should_validate_visibility_level?
|
||||
validate :check_wiki_path_conflict
|
||||
validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) }
|
||||
validates :repository_storage,
|
||||
presence: true,
|
||||
|
@ -1056,16 +1057,6 @@ class Project < ApplicationRecord
|
|||
self.errors.add(:visibility_level, _("%{level_name} is not allowed since the fork source project has lower visibility.") % { level_name: level_name })
|
||||
end
|
||||
|
||||
def check_wiki_path_conflict
|
||||
return if path.blank?
|
||||
|
||||
path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki"
|
||||
|
||||
if Project.where(namespace_id: namespace_id, path: path_to_check).exists?
|
||||
errors.add(:name, _('has already been taken'))
|
||||
end
|
||||
end
|
||||
|
||||
def pages_https_only
|
||||
return false unless Gitlab.config.pages.external_https
|
||||
|
||||
|
@ -1557,10 +1548,6 @@ class Project < ApplicationRecord
|
|||
create_repository(force: true) unless repository_exists?
|
||||
end
|
||||
|
||||
def wiki_repository_exists?
|
||||
wiki.repository_exists?
|
||||
end
|
||||
|
||||
# update visibility_level of forks
|
||||
def update_forks_visibility_level
|
||||
return if unlink_forks_upon_visibility_decrease_enabled?
|
||||
|
@ -1574,20 +1561,6 @@ class Project < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def create_wiki
|
||||
ProjectWiki.new(self, self.owner).wiki
|
||||
true
|
||||
rescue ProjectWiki::CouldNotCreateWikiError
|
||||
errors.add(:base, _('Failed create wiki'))
|
||||
false
|
||||
end
|
||||
|
||||
def wiki
|
||||
strong_memoize(:wiki) do
|
||||
ProjectWiki.new(self, self.owner)
|
||||
end
|
||||
end
|
||||
|
||||
def allowed_to_share_with_group?
|
||||
!namespace.share_with_group_lock
|
||||
end
|
||||
|
@ -2417,6 +2390,11 @@ class Project < ApplicationRecord
|
|||
jira_imports.last
|
||||
end
|
||||
|
||||
override :after_wiki_activity
|
||||
def after_wiki_activity
|
||||
touch(:last_activity_at, :last_repository_updated_at)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_service(services, name)
|
||||
|
|
|
@ -1,219 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProjectWiki
|
||||
include Storage::LegacyProjectWiki
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
class ProjectWiki < Wiki
|
||||
alias_method :project, :container
|
||||
|
||||
MARKUPS = {
|
||||
'Markdown' => :markdown,
|
||||
'RDoc' => :rdoc,
|
||||
'AsciiDoc' => :asciidoc,
|
||||
'Org' => :org
|
||||
}.freeze unless defined?(MARKUPS)
|
||||
# Project wikis are tied to the main project storage
|
||||
delegate :storage, :repository_storage, :hashed_storage?, to: :container
|
||||
|
||||
CouldNotCreateWikiError = Class.new(StandardError)
|
||||
SIDEBAR = '_sidebar'
|
||||
|
||||
TITLE_ORDER = 'title'
|
||||
CREATED_AT_ORDER = 'created_at'
|
||||
DIRECTION_DESC = 'desc'
|
||||
DIRECTION_ASC = 'asc'
|
||||
|
||||
attr_reader :project, :user
|
||||
|
||||
# Returns a string describing what went wrong after
|
||||
# an operation fails.
|
||||
attr_reader :error_message
|
||||
|
||||
def initialize(project, user = nil)
|
||||
@project = project
|
||||
@user = user
|
||||
end
|
||||
|
||||
delegate :repository_storage, :hashed_storage?, to: :project
|
||||
|
||||
def path
|
||||
@project.path + '.wiki'
|
||||
end
|
||||
|
||||
def full_path
|
||||
@project.full_path + '.wiki'
|
||||
end
|
||||
alias_method :id, :full_path
|
||||
|
||||
# @deprecated use full_path when you need it for an URL route or disk_path when you want to point to the filesystem
|
||||
alias_method :path_with_namespace, :full_path
|
||||
|
||||
def web_url(only_path: nil)
|
||||
Gitlab::UrlBuilder.build(self, only_path: only_path)
|
||||
end
|
||||
|
||||
def url_to_repo
|
||||
ssh_url_to_repo
|
||||
end
|
||||
|
||||
def ssh_url_to_repo
|
||||
Gitlab::RepositoryUrlBuilder.build(repository.full_path, protocol: :ssh)
|
||||
end
|
||||
|
||||
def http_url_to_repo
|
||||
Gitlab::RepositoryUrlBuilder.build(repository.full_path, protocol: :http)
|
||||
end
|
||||
|
||||
def wiki_base_path
|
||||
[Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/-', '/wikis'].join('')
|
||||
end
|
||||
|
||||
# Returns the Gitlab::Git::Wiki object.
|
||||
def wiki
|
||||
strong_memoize(:wiki) do
|
||||
repository.create_if_not_exists
|
||||
raise CouldNotCreateWikiError unless repository_exists?
|
||||
|
||||
Gitlab::Git::Wiki.new(repository.raw)
|
||||
end
|
||||
rescue => err
|
||||
Gitlab::ErrorTracking.track_exception(err, project_wiki: { project_id: project.id, full_path: full_path, disk_path: disk_path })
|
||||
raise CouldNotCreateWikiError
|
||||
end
|
||||
|
||||
def repository_exists?
|
||||
!!repository.exists?
|
||||
end
|
||||
|
||||
def has_home_page?
|
||||
!!find_page('home')
|
||||
end
|
||||
|
||||
def empty?
|
||||
list_pages(limit: 1).empty?
|
||||
end
|
||||
|
||||
def exists?
|
||||
!empty?
|
||||
end
|
||||
|
||||
# Lists wiki pages of the repository.
|
||||
#
|
||||
# limit - max number of pages returned by the method.
|
||||
# sort - criterion by which the pages are sorted.
|
||||
# direction - order of the sorted pages.
|
||||
# load_content - option, which specifies whether the content inside the page
|
||||
# will be loaded.
|
||||
#
|
||||
# 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)
|
||||
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
|
||||
|
||||
# Finds a page within the repository based on a tile
|
||||
# or slug.
|
||||
#
|
||||
# title - The human readable or parameterized title of
|
||||
# the page.
|
||||
#
|
||||
# Returns an initialized WikiPage instance or nil
|
||||
def find_page(title, version = nil)
|
||||
page_title, page_dir = page_title_and_dir(title)
|
||||
|
||||
if page = wiki.page(title: page_title, version: version, dir: page_dir)
|
||||
WikiPage.new(self, page)
|
||||
end
|
||||
end
|
||||
|
||||
def find_sidebar(version = nil)
|
||||
find_page(SIDEBAR, version)
|
||||
end
|
||||
|
||||
def find_file(name, version = nil)
|
||||
wiki.file(name, version)
|
||||
end
|
||||
|
||||
def create_page(title, content, format = :markdown, message = nil)
|
||||
commit = commit_details(:created, message, title)
|
||||
|
||||
wiki.write_page(title, format.to_sym, content, commit)
|
||||
|
||||
update_project_activity
|
||||
rescue Gitlab::Git::Wiki::DuplicatePageError => e
|
||||
@error_message = "Duplicate page: #{e.message}"
|
||||
false
|
||||
end
|
||||
|
||||
def update_page(page, content:, title: nil, format: :markdown, message: nil)
|
||||
commit = commit_details(:updated, message, page.title)
|
||||
|
||||
wiki.update_page(page.path, title || page.name, format.to_sym, content, commit)
|
||||
|
||||
update_project_activity
|
||||
end
|
||||
|
||||
def delete_page(page, message = nil)
|
||||
return unless page
|
||||
|
||||
wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
|
||||
|
||||
update_project_activity
|
||||
end
|
||||
|
||||
def page_title_and_dir(title)
|
||||
return unless title
|
||||
|
||||
title_array = title.split("/")
|
||||
title = title_array.pop
|
||||
[title, title_array.join("/")]
|
||||
end
|
||||
|
||||
def repository
|
||||
@repository ||= Repository.new(full_path, @project, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI)
|
||||
end
|
||||
|
||||
def default_branch
|
||||
wiki.class.default_ref
|
||||
end
|
||||
|
||||
def ensure_repository
|
||||
raise CouldNotCreateWikiError unless wiki.repository_exists?
|
||||
end
|
||||
|
||||
def hook_attrs
|
||||
{
|
||||
web_url: web_url,
|
||||
git_ssh_url: ssh_url_to_repo,
|
||||
git_http_url: http_url_to_repo,
|
||||
path_with_namespace: full_path,
|
||||
default_branch: default_branch
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def commit_details(action, message = nil, title = nil)
|
||||
commit_message = message.presence || default_message(action, title)
|
||||
git_user = Gitlab::Git::User.from_gitlab(user)
|
||||
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.id,
|
||||
git_user.username,
|
||||
git_user.name,
|
||||
git_user.email,
|
||||
commit_message)
|
||||
end
|
||||
|
||||
def default_message(action, title)
|
||||
"#{user.username} #{action} page: #{title}"
|
||||
end
|
||||
|
||||
def update_project_activity
|
||||
@project.touch(:last_activity_at, :last_repository_updated_at)
|
||||
override :disk_path
|
||||
def disk_path(*args, &block)
|
||||
container.disk_path + '.wiki'
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove this once we implement ES support for group wikis.
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/207889
|
||||
ProjectWiki.prepend_if_ee('EE::ProjectWiki')
|
||||
|
|
|
@ -1120,6 +1120,17 @@ class Repository
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: pass this in directly to `Blob` rather than delegating it to here
|
||||
#
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/201886
|
||||
def lfs_enabled?
|
||||
if container.is_a?(Project)
|
||||
container.lfs_enabled?
|
||||
else
|
||||
false # LFS is not supported for snippet or group repositories
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# TODO Genericize finder, later split this on finders by Ref or Oid
|
||||
|
|
|
@ -15,6 +15,7 @@ class Snippet < ApplicationRecord
|
|||
include FromUnion
|
||||
include IgnorableColumns
|
||||
include HasRepository
|
||||
include AfterCommitQueue
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
MAX_FILE_COUNT = 1
|
||||
|
|
|
@ -6,6 +6,7 @@ module Storage
|
|||
delegate :gitlab_shell, :repository_storage, to: :container
|
||||
|
||||
REPOSITORY_PATH_PREFIX = '@hashed'
|
||||
GROUP_REPOSITORY_PATH_PREFIX = '@groups'
|
||||
SNIPPET_REPOSITORY_PATH_PREFIX = '@snippets'
|
||||
POOL_PATH_PREFIX = '@pools'
|
||||
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Wiki
|
||||
extend ::Gitlab::Utils::Override
|
||||
include HasRepository
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
MARKUPS = { # rubocop:disable Style/MultilineIfModifier
|
||||
'Markdown' => :markdown,
|
||||
'RDoc' => :rdoc,
|
||||
'AsciiDoc' => :asciidoc,
|
||||
'Org' => :org
|
||||
}.freeze unless defined?(MARKUPS)
|
||||
|
||||
CouldNotCreateWikiError = Class.new(StandardError)
|
||||
|
||||
HOMEPAGE = 'home'
|
||||
SIDEBAR = '_sidebar'
|
||||
|
||||
TITLE_ORDER = 'title'
|
||||
CREATED_AT_ORDER = 'created_at'
|
||||
DIRECTION_DESC = 'desc'
|
||||
DIRECTION_ASC = 'asc'
|
||||
|
||||
attr_reader :container, :user
|
||||
|
||||
# Returns a string describing what went wrong after
|
||||
# an operation fails.
|
||||
attr_reader :error_message
|
||||
|
||||
def self.for_container(container, user = nil)
|
||||
"#{container.class.name}Wiki".constantize.new(container, user)
|
||||
end
|
||||
|
||||
def initialize(container, user = nil)
|
||||
@container = container
|
||||
@user = user
|
||||
end
|
||||
|
||||
def path
|
||||
container.path + '.wiki'
|
||||
end
|
||||
|
||||
# Returns the Gitlab::Git::Wiki object.
|
||||
def wiki
|
||||
strong_memoize(:wiki) do
|
||||
repository.create_if_not_exists
|
||||
raise CouldNotCreateWikiError unless repository_exists?
|
||||
|
||||
Gitlab::Git::Wiki.new(repository.raw)
|
||||
end
|
||||
rescue => err
|
||||
Gitlab::ErrorTracking.track_exception(err, wiki: {
|
||||
container_type: container.class.name,
|
||||
container_id: container.id,
|
||||
full_path: full_path,
|
||||
disk_path: disk_path
|
||||
})
|
||||
|
||||
raise CouldNotCreateWikiError
|
||||
end
|
||||
|
||||
def has_home_page?
|
||||
!!find_page(HOMEPAGE)
|
||||
end
|
||||
|
||||
def empty?
|
||||
list_pages(limit: 1).empty?
|
||||
end
|
||||
|
||||
def exists?
|
||||
!empty?
|
||||
end
|
||||
|
||||
# Lists wiki pages of the repository.
|
||||
#
|
||||
# limit - max number of pages returned by the method.
|
||||
# sort - criterion by which the pages are sorted.
|
||||
# direction - order of the sorted pages.
|
||||
# load_content - option, which specifies whether the content inside the page
|
||||
# will be loaded.
|
||||
#
|
||||
# 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)
|
||||
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
|
||||
|
||||
# Finds a page within the repository based on a tile
|
||||
# or slug.
|
||||
#
|
||||
# title - The human readable or parameterized title of
|
||||
# the page.
|
||||
#
|
||||
# Returns an initialized WikiPage instance or nil
|
||||
def find_page(title, version = nil)
|
||||
page_title, page_dir = page_title_and_dir(title)
|
||||
|
||||
if page = wiki.page(title: page_title, version: version, dir: page_dir)
|
||||
WikiPage.new(self, page)
|
||||
end
|
||||
end
|
||||
|
||||
def find_sidebar(version = nil)
|
||||
find_page(SIDEBAR, version)
|
||||
end
|
||||
|
||||
def find_file(name, version = nil)
|
||||
wiki.file(name, version)
|
||||
end
|
||||
|
||||
def create_page(title, content, format = :markdown, message = nil)
|
||||
commit = commit_details(:created, message, title)
|
||||
|
||||
wiki.write_page(title, format.to_sym, content, commit)
|
||||
|
||||
update_container_activity
|
||||
rescue Gitlab::Git::Wiki::DuplicatePageError => e
|
||||
@error_message = "Duplicate page: #{e.message}"
|
||||
false
|
||||
end
|
||||
|
||||
def update_page(page, content:, title: nil, format: :markdown, message: nil)
|
||||
commit = commit_details(:updated, message, page.title)
|
||||
|
||||
wiki.update_page(page.path, title || page.name, format.to_sym, content, commit)
|
||||
|
||||
update_container_activity
|
||||
end
|
||||
|
||||
def delete_page(page, message = nil)
|
||||
return unless page
|
||||
|
||||
wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
|
||||
|
||||
update_container_activity
|
||||
end
|
||||
|
||||
def page_title_and_dir(title)
|
||||
return unless title
|
||||
|
||||
title_array = title.split("/")
|
||||
title = title_array.pop
|
||||
[title, title_array.join("/")]
|
||||
end
|
||||
|
||||
def ensure_repository
|
||||
raise CouldNotCreateWikiError unless wiki.repository_exists?
|
||||
end
|
||||
|
||||
def hook_attrs
|
||||
{
|
||||
web_url: web_url,
|
||||
git_ssh_url: ssh_url_to_repo,
|
||||
git_http_url: http_url_to_repo,
|
||||
path_with_namespace: full_path,
|
||||
default_branch: default_branch
|
||||
}
|
||||
end
|
||||
|
||||
override :repository
|
||||
def repository
|
||||
@repository ||= Repository.new(full_path, container, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI)
|
||||
end
|
||||
|
||||
def repository_storage
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def hashed_storage?
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
override :full_path
|
||||
def full_path
|
||||
container.full_path + '.wiki'
|
||||
end
|
||||
alias_method :id, :full_path
|
||||
|
||||
# @deprecated use full_path when you need it for an URL route or disk_path when you want to point to the filesystem
|
||||
alias_method :path_with_namespace, :full_path
|
||||
|
||||
override :default_branch
|
||||
def default_branch
|
||||
wiki.class.default_ref
|
||||
end
|
||||
|
||||
def wiki_base_path
|
||||
Gitlab.config.gitlab.relative_url_root + web_url(only_path: true).sub(%r{/#{Wiki::HOMEPAGE}\z}, '')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def commit_details(action, message = nil, title = nil)
|
||||
commit_message = message.presence || default_message(action, title)
|
||||
git_user = Gitlab::Git::User.from_gitlab(user)
|
||||
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.id,
|
||||
git_user.username,
|
||||
git_user.name,
|
||||
git_user.email,
|
||||
commit_message)
|
||||
end
|
||||
|
||||
def default_message(action, title)
|
||||
"#{user.username} #{action} page: #{title}"
|
||||
end
|
||||
|
||||
def update_container_activity
|
||||
container.after_wiki_activity
|
||||
end
|
||||
end
|
||||
|
||||
Wiki.prepend_if_ee('EE::Wiki')
|
|
@ -26,7 +26,7 @@ class WikiPage
|
|||
def eql?(other)
|
||||
return false unless other.present? && other.is_a?(self.class)
|
||||
|
||||
slug == other.slug && wiki.project == other.wiki.project
|
||||
slug == other.slug && wiki.container == other.wiki.container
|
||||
end
|
||||
|
||||
alias_method :==, :eql?
|
||||
|
@ -66,9 +66,9 @@ class WikiPage
|
|||
validates :content, presence: true
|
||||
validate :validate_path_limits, if: :title_changed?
|
||||
|
||||
# The GitLab ProjectWiki instance.
|
||||
# The GitLab Wiki instance.
|
||||
attr_reader :wiki
|
||||
delegate :project, to: :wiki
|
||||
delegate :container, to: :wiki
|
||||
|
||||
# The raw Gitlab::Git::WikiPage instance.
|
||||
attr_reader :page
|
||||
|
@ -83,7 +83,7 @@ class WikiPage
|
|||
|
||||
# Construct a new WikiPage
|
||||
#
|
||||
# @param [ProjectWiki] wiki
|
||||
# @param [Wiki] wiki
|
||||
# @param [Gitlab::Git::WikiPage] page
|
||||
def initialize(wiki, page = nil)
|
||||
@wiki = wiki
|
||||
|
@ -195,7 +195,7 @@ class WikiPage
|
|||
# :content - The raw markup content.
|
||||
# :format - Optional symbol representing the
|
||||
# content format. Can be any type
|
||||
# listed in the ProjectWiki::MARKUPS
|
||||
# listed in the Wiki::MARKUPS
|
||||
# Hash.
|
||||
# :message - Optional commit message to set on
|
||||
# the new page.
|
||||
|
@ -215,7 +215,7 @@ class WikiPage
|
|||
# attrs - Hash of attributes to be updated on the page.
|
||||
# :content - The raw markup content to replace the existing.
|
||||
# :format - Optional symbol representing the content format.
|
||||
# See ProjectWiki::MARKUPS Hash for available formats.
|
||||
# See Wiki::MARKUPS Hash for available formats.
|
||||
# :message - Optional commit message to set on the new version.
|
||||
# :last_commit_sha - Optional last commit sha to validate the page unchanged.
|
||||
# :title - The Title (optionally including dir) to replace existing title
|
||||
|
@ -261,6 +261,7 @@ class WikiPage
|
|||
# Relative path to the partial to be used when rendering collections
|
||||
# of this object.
|
||||
def to_partial_path
|
||||
# TODO: Move into shared/ with https://gitlab.com/gitlab-org/gitlab/-/issues/196054
|
||||
'projects/wikis/wiki_page'
|
||||
end
|
||||
|
||||
|
@ -303,7 +304,7 @@ class WikiPage
|
|||
end
|
||||
|
||||
def update_front_matter(attrs)
|
||||
return unless Gitlab::WikiPages::FrontMatterParser.enabled?(project)
|
||||
return unless Gitlab::WikiPages::FrontMatterParser.enabled?(container)
|
||||
return unless attrs.has_key?(:front_matter)
|
||||
|
||||
fm_yaml = serialize_front_matter(attrs[:front_matter])
|
||||
|
@ -314,7 +315,7 @@ class WikiPage
|
|||
|
||||
def parsed_content
|
||||
strong_memoize(:parsed_content) do
|
||||
Gitlab::WikiPages::FrontMatterParser.new(raw_content, project).parse
|
||||
Gitlab::WikiPages::FrontMatterParser.new(raw_content, container).parse
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class WikiPagePolicy < BasePolicy
|
||||
delegate { @subject.wiki.project }
|
||||
delegate { @subject.wiki.container }
|
||||
|
||||
rule { can?(:read_wiki) }.enable :read_wiki_page
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Create cluster annotations API endpoint
|
||||
merge_request: 29502
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Increase label list label column width
|
||||
merge_request: 29963
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Route to feature flags based on internal id
|
||||
merge_request: 29740
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix bug in personal snippets when somebody is mentioned
|
||||
merge_request: 29835
|
||||
author: Sashi Kumar
|
||||
type: fixed
|
|
@ -180,7 +180,7 @@ Plan.default.limits.update!(ci_pipeline_schedules: 100)
|
|||
|
||||
### Incident Management inbound alert limits
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14932) in GitLab 12.5.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17859) in GitLab 12.5.
|
||||
|
||||
Limiting inbound alerts for an incident reduces the number of alerts (issues)
|
||||
that can be created within a period of time, which can help prevent overloading
|
||||
|
@ -192,7 +192,7 @@ alerts in the following ways:
|
|||
|
||||
### Prometheus Alert JSON payloads
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14929) in GitLab 12.6.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19940) in GitLab 12.6.
|
||||
|
||||
Prometheus alert payloads sent to the `notify.json` endpoint are limited to 1 MB in size.
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# Troubleshooting Elasticsearch
|
||||
|
||||
To install and configure Elasticsearch, and for common and known issues,
|
||||
visit the [administrator documentation](../../integration/elasticsearch.md).
|
||||
|
||||
Troubleshooting Elasticsearch requires:
|
||||
|
||||
- Knowledge of common terms.
|
||||
|
|
|
@ -20,7 +20,7 @@ You will need to replace the `vault.example.com` URL below with the URL of your
|
|||
|
||||
## How it works
|
||||
|
||||
Each job has JSON Web Token (JWT) provided as environment variable named `CI_JOB_JWT`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt/#jwt-authentication) method.
|
||||
Each job has JSON Web Token (JWT) provided as environment variable named `CI_JOB_JWT`. This JWT can be used to authenticate with Vault using the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt#jwt-authentication) method.
|
||||
|
||||
The JWT's payload looks like this:
|
||||
|
||||
|
@ -51,7 +51,7 @@ The JWT is encoded by using RS256 and signed with your GitLab instance's OpenID
|
|||
|
||||
You can use this JWT and your instance's JWKS endpoint (`https://gitlab.example.com/-/jwks`) to authenticate with a Vault server that is configured to allow the JWT Authentication method for authentication.
|
||||
|
||||
When configuring roles in Vault, you can use [bound_claims](https://www.vaultproject.io/docs/auth/jwt/#bound-claims) to match against the JWT's claims and restrict which secrets each CI job has access to.
|
||||
When configuring roles in Vault, you can use [bound_claims](https://www.vaultproject.io/docs/auth/jwt#bound-claims) to match against the JWT's claims and restrict which secrets each CI job has access to.
|
||||
|
||||
To communicate with Vault, you can use either its CLI client or perform API requests (using `curl` or another client).
|
||||
|
||||
|
@ -70,7 +70,7 @@ $ vault kv get -field=password secret/myproject/production/db
|
|||
real-pa$$w0rd
|
||||
```
|
||||
|
||||
To configure your Vault server, start by enabling the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt/) method:
|
||||
To configure your Vault server, start by enabling the [JWT Auth](https://www.vaultproject.io/docs/auth/jwt) method:
|
||||
|
||||
```shell
|
||||
$ vault auth enable jwt
|
||||
|
|
|
@ -113,7 +113,7 @@ There are some important differences in the way Runners work in comparison to ag
|
|||
|
||||
If you are using `gitlab.com`, you can take advantage of our [shared Runner fleet](../../user/gitlab_com/index.md#shared-runners)
|
||||
to run jobs without provisioning your own Runners. We are investigating making them
|
||||
[available for self-managed instances](https://gitlab.com/gitlab-org/customers-gitlab-com/issues/414)
|
||||
[available for self-managed instances](https://gitlab.com/groups/gitlab-org/-/epics/835)
|
||||
as well.
|
||||
|
||||
## Groovy vs. YAML
|
||||
|
|
|
@ -12,7 +12,7 @@ which is exposed as an API endpoint at `/api/graphql`.
|
|||
|
||||
## Deep Dive
|
||||
|
||||
In March 2019, Nick Thomas hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1)
|
||||
In March 2019, Nick Thomas hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
|
||||
on GitLab's [GraphQL API](../api/graphql/index.md) to share his domain specific knowledge
|
||||
with anyone who may work in this part of the code base in the future. You can find the
|
||||
[recording on YouTube](https://www.youtube.com/watch?v=-9L_1MWrjkg), and the slides on
|
||||
|
@ -102,7 +102,7 @@ be `id` fields.
|
|||
Further reading:
|
||||
|
||||
- [GraphQL Best Practices Guide](https://graphql.org/learn/best-practices/#nullability)
|
||||
- [Using nullability in GraphQL](https://blog.apollographql.com/using-nullability-in-graphql-2254f84c4ed7)
|
||||
- [Using nullability in GraphQL](https://www.apollographql.com/blog/using-nullability-in-graphql-2254f84c4ed7)
|
||||
|
||||
### Exposing Global IDs
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ data](https://gitlab.com/gitlab-org/gitlab/blob/19f74903240e209736c7668132e6a5a7
|
|||
for `Todo` _targets_ when returned in the Todos API.
|
||||
|
||||
For more context and discussion about preloading see
|
||||
[this merge request](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/25711)
|
||||
[this merge request](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25711)
|
||||
which introduced the scope.
|
||||
|
||||
### Verifying with tests
|
||||
|
|
|
@ -8,7 +8,7 @@ Currently we rely on different sources to present diffs, these include:
|
|||
|
||||
## Deep Dive
|
||||
|
||||
In January 2019, Oswaldo Ferreira hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1) on GitLab's Diffs and Commenting on Diffs functionality to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=K6G3gMcFyek), and the slides on [Google Slides](https://docs.google.com/presentation/d/1bGutFH2AT3bxOPZuLMGl1ANWHqFnrxwQwjiwAZkF-TU/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/b5ad2f336e0afcfe0f99db0af0ccc71a/). Everything covered in this deep dive was accurate as of GitLab 11.7, and while specific details may have changed since then, it should still serve as a good introduction.
|
||||
In January 2019, Oswaldo Ferreira hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's Diffs and Commenting on Diffs functionality to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=K6G3gMcFyek), and the slides on [Google Slides](https://docs.google.com/presentation/d/1bGutFH2AT3bxOPZuLMGl1ANWHqFnrxwQwjiwAZkF-TU/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/b5ad2f336e0afcfe0f99db0af0ccc71a/). Everything covered in this deep dive was accurate as of GitLab 11.7, and while specific details may have changed since then, it should still serve as a good introduction.
|
||||
|
||||
## Architecture overview
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ the [Elasticsearch integration documentation](../integration/elasticsearch.md#en
|
|||
|
||||
## Deep Dive
|
||||
|
||||
In June 2019, Mario de la Ossa hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1) on GitLab's [Elasticsearch integration](../integration/elasticsearch.md) to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=vrvl-tN2EaA), and the slides on [Google Slides](https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf). Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details may have changed since then, it should still serve as a good introduction.
|
||||
In June 2019, Mario de la Ossa hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`) on GitLab's [Elasticsearch integration](../integration/elasticsearch.md) to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=vrvl-tN2EaA), and the slides on [Google Slides](https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit) and in [PDF](https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf). Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details may have changed since then, it should still serve as a good introduction.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ Workhorse and GitLab-Shell.
|
|||
|
||||
## Deep Dive
|
||||
|
||||
In May 2019, Bob Van Landuyt hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1)
|
||||
In May 2019, Bob Van Landuyt hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
|
||||
on GitLab's [Gitaly project](https://gitlab.com/gitlab-org/gitaly) and how to contribute to it as a
|
||||
Ruby developer, to share his domain specific knowledge with anyone who may work in this part of the
|
||||
code base in the future.
|
||||
|
|
|
@ -87,7 +87,7 @@ are very appreciative of the work done by translators and proofreaders!
|
|||
- Mark Minakou - [GitLab](https://gitlab.com/sandzhaj), [Crowdin](https://crowdin.com/profile/sandzhaj)
|
||||
- NickVolynkin - [Crowdin](https://crowdin.com/profile/NickVolynkin)
|
||||
- Andrey Komarov - [GitLab](https://gitlab.com/elkamarado), [Crowdin](https://crowdin.com/profile/kamarado)
|
||||
- Iaroslav Postovalov - [GitLab](https://gitlab/CMDR_Tvis), [Crowdin](https://crowdin.com/profile/CMDR_Tvis)
|
||||
- Iaroslav Postovalov - [GitLab](https://gitlab.com/CMDR_Tvis), [Crowdin](https://crowdin.com/profile/CMDR_Tvis)
|
||||
- Serbian (Cyrillic)
|
||||
- Proofreaders needed.
|
||||
- Serbian (Latin)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Deep Dive
|
||||
|
||||
In April 2019, Francisco Javier López hosted a [Deep Dive](https://gitlab.com/gitlab-org/create-stage/issues/1)
|
||||
In April 2019, Francisco Javier López hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
|
||||
on GitLab's [Git LFS](../topics/git/lfs/index.md) implementation to share his domain
|
||||
specific knowledge with anyone who may work in this part of the code base in the future.
|
||||
You can find the [recording on YouTube](https://www.youtube.com/watch?v=Yyxwcksr0Qc),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Deep Dive
|
||||
|
||||
In December 2018, Tiago Botelho hosted [a Deep Dive](`https://gitlab.com/gitlab-org/create-stage/issues/1`)
|
||||
In December 2018, Tiago Botelho hosted a Deep Dive (GitLab team members only: `https://gitlab.com/gitlab-org/create-stage/issues/1`)
|
||||
on GitLab's [Pull Repository Mirroring functionality](../user/project/repository/repository_mirroring.md#pulling-from-a-remote-repository-starter)
|
||||
to share his domain specific knowledge with anyone who may work in this part of the
|
||||
code base in the future. You can find the [recording on YouTube](https://www.youtube.com/watch?v=sSZq0fpdY-Y),
|
||||
|
|
|
@ -26,7 +26,7 @@ Improper permission handling can have significant impacts on the security of an
|
|||
Some situations may reveal [sensitive data](https://gitlab.com/gitlab-com/gl-infra/production/issues/477) or allow a malicious actor to perform [harmful actions](https://gitlab.com/gitlab-org/gitlab/issues/8180).
|
||||
The overall impact depends heavily on what resources can be accessed or modified improperly.
|
||||
|
||||
A common vulnerability when permission checks are missing is called [IDOR](https://www.owasp.org/index.php/Testing_for_Insecure_Direct_Object_References_(OTG-AUTHZ-004)) for Insecure Direct Object References.
|
||||
A common vulnerability when permission checks are missing is called [IDOR](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/05-Authorization_Testing/04-Testing_for_Insecure_Direct_Object_References) for Insecure Direct Object References.
|
||||
|
||||
### When to Consider
|
||||
|
||||
|
@ -49,8 +49,8 @@ Be careful to **also test [visibility levels](https://gitlab.com/gitlab-org/gitl
|
|||
Some example of well implemented access controls and tests:
|
||||
|
||||
1. [example1](https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/710/diffs?diff_id=13750#af40ef0eaae3c1e018809e1d88086e32bccaca40_43_43)
|
||||
1. [example2](https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2511/diffs#ed3aaab1510f43b032ce345909a887e5b167e196_142_155)
|
||||
1. [example3](https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3170/diffs?diff_id=17494)
|
||||
1. [example2](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/2511/diffs#ed3aaab1510f43b032ce345909a887e5b167e196_142_155)
|
||||
1. [example3](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/3170/diffs?diff_id=17494)
|
||||
|
||||
**NB:** any input from development team is welcome, e.g. about rubocop rules.
|
||||
|
||||
|
@ -209,7 +209,7 @@ In some cases, it has been possible to configure GitLab::HTTP as the HTTP
|
|||
connection library for 3rd-party gems. This is preferrable over re-implementing
|
||||
the mitigations for a new feature.
|
||||
|
||||
- [More details](https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/2530/diffs)
|
||||
- [More details](https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/2530/diffs)
|
||||
|
||||
#### Feature-specific Mitigations
|
||||
|
||||
|
@ -279,7 +279,7 @@ For any and all input fields, ensure to define expectations on the type/format o
|
|||
- Validate the input using a [whitelist approach](https://youtu.be/2VFavqfDS6w?t=7816) to only allow characters through which you are expecting to receive for the field.
|
||||
- Input which fails validation should be **rejected**, and not sanitized.
|
||||
|
||||
Note that blacklists should be avoided, as it is near impossible to block all [variations of XSS](https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet).
|
||||
Note that blacklists should be avoided, as it is near impossible to block all [variations of XSS](https://owasp.org/www-community/xss-filter-evasion-cheatsheet).
|
||||
|
||||
#### Output encoding
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ We follow a simple formula roughly based on hungarian notation.
|
|||
- `_placeholder`: a temporary element that appears while content is loading. For example, the elements that are displayed instead of discussions while the discussions are being fetched.
|
||||
- `_radio`
|
||||
- `_tab`
|
||||
- `_menu_item`
|
||||
|
||||
*Note: If none of the listed types are suitable, please open a merge request to add an appropriate type to the list.*
|
||||
|
||||
|
|
|
@ -497,6 +497,8 @@ However, some larger installations may wish to tune the merge policy settings:
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
### Common issues
|
||||
|
||||
Here are some common pitfalls and how to overcome them:
|
||||
|
||||
- **How can I verify my GitLab instance is using Elasticsearch?**
|
||||
|
@ -625,6 +627,10 @@ Here are some common pitfalls and how to overcome them:
|
|||
You probably have not used either `http://` or `https://` as part of your value in the **"URL"** field of the Elasticseach Integration Menu. Please make sure you are using either `http://` or `https://` in this field as the [Elasticsearch client for Go](https://github.com/olivere/elastic) that we are using [needs the prefix for the URL to be accepted as valid](https://github.com/olivere/elastic/commit/a80af35aa41856dc2c986204e2b64eab81ccac3a).
|
||||
Once you have corrected the formatting of the URL, delete the index (via the [dedicated Rake task](#gitlab-elasticsearch-rake-tasks)) and [reindex the content of your instance](#adding-gitlabs-data-to-the-elasticsearch-index).
|
||||
|
||||
### Low level troubleshooting
|
||||
|
||||
There is more [low level troubleshooting documentation](../administration/troubleshooting/elasticsearch.md) for when you experience other issues, including poor performance.
|
||||
|
||||
### Known Issues
|
||||
|
||||
- **[Elasticsearch `code_analyzer` doesn't account for all code cases](https://gitlab.com/gitlab-org/gitlab/issues/10693)**
|
||||
|
|
|
@ -4,7 +4,7 @@ type: reference
|
|||
|
||||
# Rate limits on issue creation
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/55241) in GitLab 12.10.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28129) in GitLab 12.10.
|
||||
|
||||
This setting allows you to rate limit the requests to the issue creation endpoint.
|
||||
It defaults to 300 requests per minute.
|
||||
|
|
|
@ -67,6 +67,26 @@ current default comparison.
|
|||
|
||||
![Merge request versions compare HEAD](img/versions_compare_head_v12_10.png)
|
||||
|
||||
### Enable or disable `HEAD` comparison mode **(CORE ONLY)**
|
||||
|
||||
`HEAD` comparison mode is under development and not ready for production use. It is
|
||||
deployed behind a feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session)
|
||||
can enable it for your instance. You're welcome to test it, but use it at your
|
||||
own risk.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:diff_compare_with_head)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:diff_compare_with_head)
|
||||
```
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
# Advanced Global Search **(STARTER ONLY)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109) in GitLab [Starter](https://about.gitlab.com/pricing/) 8.4.
|
||||
> - This is the user documentation. To install and configure Elasticsearch,
|
||||
> visit the [administrator documentation](../../integration/elasticsearch.md).
|
||||
|
||||
NOTE: **Note**
|
||||
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
|
||||
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it.
|
||||
[Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
|
||||
|
||||
Leverage Elasticsearch for faster, more advanced code search across your entire
|
||||
GitLab instance.
|
||||
|
||||
This is the user documentation. To install and configure Elasticsearch,
|
||||
visit the [administrator documentation](../../integration/elasticsearch.md).
|
||||
|
||||
## Overview
|
||||
|
||||
The Advanced Global Search in GitLab is a powerful search service that saves
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
> **Notes:**
|
||||
>
|
||||
> - Introduced in [GitLab Enterprise Starter](https://about.gitlab.com/pricing/) 9.2
|
||||
> - This is the user documentation. To install and configure Elasticsearch,
|
||||
> visit the [administrator documentation](../../integration/elasticsearch.md).
|
||||
|
||||
NOTE: **Note**
|
||||
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it. [Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
|
||||
Advanced Global Search (powered by Elasticsearch) is not yet available on GitLab.com. We are working on adding it.
|
||||
[Follow this epic for the latest updates](https://gitlab.com/groups/gitlab-org/-/epics/153).
|
||||
|
||||
Use advanced queries for more targeted search results.
|
||||
|
||||
This is the user documentation. To install and configure Elasticsearch,
|
||||
visit the [administrator documentation](../../integration/elasticsearch.md).
|
||||
|
||||
## Overview
|
||||
|
||||
The Advanced Syntax Search is a subset of the
|
||||
|
|
|
@ -18,20 +18,29 @@ module API
|
|||
requires :description, type: String, desc: 'The description of the annotation'
|
||||
end
|
||||
|
||||
resource :environments do
|
||||
post ':id/metrics_dashboard/annotations' do
|
||||
environment = ::Environment.find(params[:id])
|
||||
ANNOTATIONS_SOURCES = [
|
||||
{ class: ::Environment, resource: :environments, create_service_param_key: :environment },
|
||||
{ class: Clusters::Cluster, resource: :clusters, create_service_param_key: :cluster }
|
||||
].freeze
|
||||
|
||||
not_found! unless Feature.enabled?(:metrics_dashboard_annotations, environment.project)
|
||||
ANNOTATIONS_SOURCES.each do |annotations_source|
|
||||
resource annotations_source[:resource] do
|
||||
post ':id/metrics_dashboard/annotations' do
|
||||
annotations_source_object = annotations_source[:class].find(params[:id])
|
||||
|
||||
forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, environment)
|
||||
not_found! unless Feature.enabled?(:metrics_dashboard_annotations, annotations_source_object.project)
|
||||
|
||||
result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, declared(params).merge(environment: environment)).execute
|
||||
forbidden! unless can?(current_user, :create_metrics_dashboard_annotation, annotations_source_object)
|
||||
|
||||
if result[:status] == :success
|
||||
present result[:annotation], with: Entities::Metrics::Dashboard::Annotation
|
||||
else
|
||||
error!(result, 400)
|
||||
create_service_params = declared(params).merge(annotations_source[:create_service_param_key] => annotations_source_object)
|
||||
|
||||
result = ::Metrics::Dashboard::Annotations::CreateService.new(current_user, create_service_params).execute
|
||||
|
||||
if result[:status] == :success
|
||||
present result[:annotation], with: Entities::Metrics::Dashboard::Annotation
|
||||
else
|
||||
error!(result, 400)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,8 +34,8 @@ module Gitlab
|
|||
snippet_url(object, **options)
|
||||
when User
|
||||
instance.user_url(object, **options)
|
||||
when ProjectWiki
|
||||
instance.project_wiki_url(object.project, :home, **options)
|
||||
when Wiki
|
||||
wiki_url(object, **options)
|
||||
when WikiPage
|
||||
instance.project_wiki_url(object.wiki.project, object.slug, **options)
|
||||
else
|
||||
|
@ -70,6 +70,19 @@ module Gitlab
|
|||
instance.gitlab_snippet_url(snippet, **options)
|
||||
end
|
||||
end
|
||||
|
||||
def wiki_url(object, **options)
|
||||
case object.container
|
||||
when Project
|
||||
instance.project_wiki_url(object.container, Wiki::HOMEPAGE, **options)
|
||||
when Group
|
||||
# TODO: Use the new route for group wikis once we add it.
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/211360
|
||||
instance.group_canonical_url(object.container, **options) + "/-/wikis/#{Wiki::HOMEPAGE}"
|
||||
else
|
||||
raise NotImplementedError.new("No URL builder defined for #{object.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8723,9 +8723,6 @@ msgstr ""
|
|||
msgid "Failed Jobs"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed create wiki"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to add a Zoom meeting"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8765,6 +8762,9 @@ msgstr ""
|
|||
msgid "Failed to create resources"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to create wiki"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to delete board. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -25,11 +25,11 @@ FactoryBot.define do
|
|||
|
||||
factory :wiki_page_event do
|
||||
action { Event::CREATED }
|
||||
project { @overrides[:wiki_page]&.project || create(:project, :wiki_repo) }
|
||||
project { @overrides[:wiki_page]&.container || create(:project, :wiki_repo) }
|
||||
target { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) }
|
||||
|
||||
transient do
|
||||
wiki_page { create(:wiki_page, project: project) }
|
||||
wiki_page { create(:wiki_page, container: project) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -51,5 +51,11 @@ FactoryBot.define do
|
|||
trait :owner_subgroup_creation_only do
|
||||
subgroup_creation_level { ::Gitlab::Access::OWNER_SUBGROUP_ACCESS}
|
||||
end
|
||||
|
||||
trait :wiki_repo do
|
||||
after(:create) do |group|
|
||||
raise 'Failed to create wiki repository!' unless group.create_wiki
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :project_wiki do
|
||||
skip_create
|
||||
|
||||
association :project, :wiki_repo
|
||||
user { project.creator }
|
||||
initialize_with { new(project, user) }
|
||||
end
|
||||
end
|
|
@ -8,7 +8,8 @@ FactoryBot.define do
|
|||
title { generate(:wiki_page_title) }
|
||||
content { 'Content for wiki page' }
|
||||
format { 'markdown' }
|
||||
project { create(:project) }
|
||||
project { association(:project, :wiki_repo) }
|
||||
container { project }
|
||||
attrs do
|
||||
{
|
||||
title: title,
|
||||
|
@ -19,7 +20,7 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
page { OpenStruct.new(url_path: 'some-name') }
|
||||
wiki { build(:project_wiki, project: project) }
|
||||
wiki { association(:wiki, container: container) }
|
||||
|
||||
initialize_with { new(wiki, page) }
|
||||
|
||||
|
@ -32,8 +33,6 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
trait :with_real_page do
|
||||
project { create(:project, :repository) }
|
||||
|
||||
page do
|
||||
wiki.create_page(title, content)
|
||||
page_title, page_dir = wiki.page_title_and_dir(title)
|
||||
|
@ -48,10 +47,10 @@ FactoryBot.define do
|
|||
|
||||
trait :for_wiki_page do
|
||||
transient do
|
||||
wiki_page { create(:wiki_page, project: project) }
|
||||
wiki_page { create(:wiki_page, container: project) }
|
||||
end
|
||||
|
||||
project { @overrides[:wiki_page]&.project || create(:project) }
|
||||
project { @overrides[:wiki_page]&.container || create(:project) }
|
||||
title { wiki_page.title }
|
||||
|
||||
initialize_with do
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :wiki do
|
||||
transient do
|
||||
container { association(:project, :wiki_repo) }
|
||||
user { association(:user) }
|
||||
end
|
||||
|
||||
initialize_with { Wiki.for_container(container, user) }
|
||||
skip_create
|
||||
|
||||
factory :project_wiki do
|
||||
transient do
|
||||
project { association(:project, :wiki_repo) }
|
||||
end
|
||||
|
||||
container { project }
|
||||
end
|
||||
|
||||
factory :group_wiki do
|
||||
container { association(:group, :wiki_repo) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,6 +18,7 @@ describe('Compare diff version dropdowns', () => {
|
|||
};
|
||||
localState.targetBranchName = 'baseVersion';
|
||||
localState.mergeRequestDiffs = diffsMockData;
|
||||
gon.features = { diffCompareWithHead: true };
|
||||
});
|
||||
|
||||
describe('selectedTargetIndex', () => {
|
||||
|
@ -128,6 +129,14 @@ describe('Compare diff version dropdowns', () => {
|
|||
});
|
||||
assertVersions(targetVersions);
|
||||
});
|
||||
|
||||
it('does not list head version if feature flag is not enabled', () => {
|
||||
gon.features = { diffCompareWithHead: false };
|
||||
setupTest();
|
||||
const targetVersions = getters.diffCompareDropdownTargetVersions(localState, getters);
|
||||
|
||||
expect(targetVersions.find(version => version.isHead)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('diffCompareDropdownSourceVersions', () => {
|
||||
|
|
|
@ -210,4 +210,46 @@ describe('vue_shared/components/awards_list', () => {
|
|||
expect(buttons.wrappers.every(x => x.classes('disabled'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with default awards', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
awards: [createAward(EMOJI_SMILE, USERS.marie), createAward(EMOJI_100, USERS.marie)],
|
||||
canAwardEmoji: true,
|
||||
currentUserId: USERS.root.id,
|
||||
// Let's assert that it puts thumbsup and thumbsdown in the right order still
|
||||
defaultAwards: [EMOJI_THUMBSDOWN, EMOJI_100, EMOJI_THUMBSUP],
|
||||
});
|
||||
});
|
||||
|
||||
it('shows awards in correct order', () => {
|
||||
expect(findAwardsData()).toEqual([
|
||||
{
|
||||
classes: ['btn', 'award-control'],
|
||||
count: 0,
|
||||
html: matchingEmojiTag(EMOJI_THUMBSUP),
|
||||
title: '',
|
||||
},
|
||||
{
|
||||
classes: ['btn', 'award-control'],
|
||||
count: 0,
|
||||
html: matchingEmojiTag(EMOJI_THUMBSDOWN),
|
||||
title: '',
|
||||
},
|
||||
// We expect the EMOJI_100 before the EMOJI_SMILE because it was given as a defaultAward
|
||||
{
|
||||
classes: ['btn', 'award-control'],
|
||||
count: 1,
|
||||
html: matchingEmojiTag(EMOJI_100),
|
||||
title: 'Marie',
|
||||
},
|
||||
{
|
||||
classes: ['btn', 'award-control'],
|
||||
count: 1,
|
||||
html: matchingEmojiTag(EMOJI_SMILE),
|
||||
title: 'Marie',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe EventsHelper do
|
||||
include Gitlab::Routing
|
||||
|
||||
describe '#event_commit_title' do
|
||||
let(:message) { 'foo & bar ' + 'A' * 70 + '\n' + 'B' * 80 }
|
||||
|
||||
|
|
|
@ -59,8 +59,8 @@ describe Banzai::Pipeline::WikiPipeline do
|
|||
let(:project_wiki) { ProjectWiki.new(project, double(:user)) }
|
||||
let(:page) { build(:wiki_page, wiki: project_wiki, page: OpenStruct.new(url_path: 'nested/twice/start-page')) }
|
||||
|
||||
{ "when GitLab is hosted at a root URL" => '/',
|
||||
"when GitLab is hosted at a relative URL" => '/nested/relative/gitlab' }.each do |test_name, relative_url_root|
|
||||
{ 'when GitLab is hosted at a root URL' => '',
|
||||
'when GitLab is hosted at a relative URL' => '/nested/relative/gitlab' }.each do |test_name, relative_url_root|
|
||||
context test_name do
|
||||
before do
|
||||
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return(relative_url_root)
|
||||
|
|
|
@ -52,14 +52,10 @@ describe Gitlab::GitAccessWiki do
|
|||
end
|
||||
|
||||
context 'when the wiki repository does not exist' do
|
||||
it 'returns not found' do
|
||||
wiki_repo = project.wiki.repository
|
||||
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
|
||||
FileUtils.rm_rf(wiki_repo.path)
|
||||
end
|
||||
let(:project) { create(:project) }
|
||||
|
||||
# Sanity check for rm_rf
|
||||
expect(wiki_repo.exists?).to eq(false)
|
||||
it 'returns not found' do
|
||||
expect(project.wiki_repository_exists?).to eq(false)
|
||||
|
||||
expect { subject }.to raise_error(Gitlab::GitAccess::NotFoundError, 'A repository for this project does not exist yet.')
|
||||
end
|
||||
|
|
|
@ -9,7 +9,8 @@ describe Gitlab::RepositoryUrlBuilder do
|
|||
where(:factory, :path_generator) do
|
||||
:project | ->(project) { project.full_path }
|
||||
:project_snippet | ->(snippet) { "#{snippet.project.full_path}/snippets/#{snippet.id}" }
|
||||
:project_wiki | ->(wiki) { "#{wiki.project.full_path}.wiki" }
|
||||
:project_wiki | ->(wiki) { "#{wiki.container.full_path}.wiki" }
|
||||
:group_wiki | ->(wiki) { "#{wiki.container.full_path}.wiki" }
|
||||
|
||||
:personal_snippet | ->(snippet) { "snippets/#{snippet.id}" }
|
||||
end
|
||||
|
|
|
@ -23,11 +23,12 @@ describe Gitlab::UrlBuilder do
|
|||
:merge_request | ->(merge_request) { "/#{merge_request.project.full_path}/-/merge_requests/#{merge_request.iid}" }
|
||||
:project_milestone | ->(milestone) { "/#{milestone.project.full_path}/-/milestones/#{milestone.iid}" }
|
||||
:project_snippet | ->(snippet) { "/#{snippet.project.full_path}/snippets/#{snippet.id}" }
|
||||
:project_wiki | ->(wiki) { "/#{wiki.project.full_path}/-/wikis/home" }
|
||||
:project_wiki | ->(wiki) { "/#{wiki.container.full_path}/-/wikis/home" }
|
||||
:ci_build | ->(build) { "/#{build.project.full_path}/-/jobs/#{build.id}" }
|
||||
|
||||
:group | ->(group) { "/groups/#{group.full_path}" }
|
||||
:group_milestone | ->(milestone) { "/groups/#{milestone.group.full_path}/-/milestones/#{milestone.iid}" }
|
||||
:group_wiki | ->(wiki) { "/groups/#{wiki.container.full_path}/-/wikis/home" }
|
||||
|
||||
:user | ->(user) { "/#{user.full_path}" }
|
||||
:personal_snippet | ->(snippet) { "/snippets/#{snippet.id}" }
|
||||
|
|
|
@ -5,12 +5,17 @@ require 'spec_helper'
|
|||
describe Blob do
|
||||
include FakeBlobHelpers
|
||||
|
||||
let(:project) { build(:project, lfs_enabled: true) }
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:project) { build(:project) }
|
||||
let(:personal_snippet) { build(:personal_snippet) }
|
||||
let(:project_snippet) { build(:project_snippet, project: project) }
|
||||
|
||||
let(:repository) { project.repository }
|
||||
let(:lfs_enabled) { true }
|
||||
|
||||
before do
|
||||
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
|
||||
allow(repository).to receive(:lfs_enabled?) { lfs_enabled }
|
||||
end
|
||||
|
||||
describe '.decorate' do
|
||||
|
@ -128,399 +133,84 @@ describe Blob do
|
|||
end
|
||||
|
||||
describe '#external_storage_error?' do
|
||||
shared_examples 'no error' do
|
||||
it do
|
||||
expect(blob.external_storage_error?).to be_falsey
|
||||
end
|
||||
end
|
||||
subject { blob.external_storage_error? }
|
||||
|
||||
shared_examples 'returns error' do
|
||||
it do
|
||||
expect(blob.external_storage_error?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the blob is stored in LFS' do
|
||||
let(:blob) { fake_blob(path: 'file.pdf', lfs: true, container: container) }
|
||||
|
||||
context 'when the project has LFS enabled' do
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'no error'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns error'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'no error'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project does not have LFS enabled' do
|
||||
before do
|
||||
project.lfs_enabled = false
|
||||
end
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns error'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the blob is not stored in LFS' do
|
||||
let(:blob) { fake_blob(path: 'file.md', container: container) }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'no error'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'no error'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'no error'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stored_externally?' do
|
||||
context 'if the blob is stored in LFS' do
|
||||
let(:blob) { fake_blob(path: 'file.pdf', lfs: true) }
|
||||
|
||||
shared_examples 'returns true' do
|
||||
it do
|
||||
expect(blob.stored_externally?).to be_truthy
|
||||
end
|
||||
context 'when LFS is enabled' do
|
||||
let(:lfs_enabled) { true }
|
||||
|
||||
it { is_expected.to be_falsy }
|
||||
end
|
||||
|
||||
shared_examples 'returns false' do
|
||||
it do
|
||||
expect(blob.stored_externally?).to be_falsey
|
||||
end
|
||||
end
|
||||
context 'when LFS is not enabled' do
|
||||
let(:lfs_enabled) { false }
|
||||
|
||||
context 'when the project has LFS enabled' do
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project does not have LFS enabled' do
|
||||
before do
|
||||
project.lfs_enabled = false
|
||||
end
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the blob is not stored in LFS' do
|
||||
let(:blob) { fake_blob(path: 'file.md') }
|
||||
|
||||
it 'returns false' do
|
||||
expect(blob.stored_externally?).to be_falsey
|
||||
it { is_expected.to be_falsy }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#stored_externally?' do
|
||||
subject { blob.stored_externally? }
|
||||
|
||||
context 'if the blob is stored in LFS' do
|
||||
let(:blob) { fake_blob(path: 'file.pdf', lfs: true) }
|
||||
|
||||
context 'when LFS is enabled' do
|
||||
let(:lfs_enabled) { true }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when LFS is not enabled' do
|
||||
let(:lfs_enabled) { false }
|
||||
|
||||
it { is_expected.to be_falsy }
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the blob is not stored in LFS' do
|
||||
let(:blob) { fake_blob(path: 'file.md') }
|
||||
|
||||
it { is_expected.to be_falsy }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#binary?' do
|
||||
shared_examples 'returns true' do
|
||||
it do
|
||||
expect(blob.binary?).to be_truthy
|
||||
context 'an lfs object' do
|
||||
where(:filename, :is_binary) do
|
||||
'file.pdf' | true
|
||||
'file.md' | false
|
||||
'file.txt' | false
|
||||
'file.ics' | false
|
||||
'file.rb' | false
|
||||
'file.exe' | true
|
||||
'file.ini' | false
|
||||
'file.wtf' | true
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:blob) { fake_blob(path: filename, lfs: true, container: project) }
|
||||
|
||||
it { expect(blob.binary?).to eq(is_binary) }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'returns false' do
|
||||
it do
|
||||
expect(blob.binary?).to be_falsey
|
||||
end
|
||||
end
|
||||
context 'a non-lfs object' do
|
||||
let(:blob) { fake_blob(path: 'anything', container: project) }
|
||||
|
||||
context 'if the blob is stored externally' do
|
||||
let(:blob) { fake_blob(path: file, lfs: true) }
|
||||
it 'delegates to binary_in_repo?' do
|
||||
expect(blob).to receive(:binary_in_repo?) { :result }
|
||||
|
||||
context 'if the extension has a rich viewer' do
|
||||
context 'if the viewer is binary' do
|
||||
let(:file) { 'file.pdf' }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the viewer is text-based' do
|
||||
let(:file) { 'file.md' }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "if the extension doesn't have a rich viewer" do
|
||||
context 'if the extension has a text mime type' do
|
||||
context 'if the extension is for a programming language' do
|
||||
let(:file) { 'file.txt' }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the extension is not for a programming language' do
|
||||
let(:file) { 'file.ics' }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the extension has a binary mime type' do
|
||||
context 'if the extension is for a programming language' do
|
||||
let(:file) { 'file.rb' }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the extension is not for a programming language' do
|
||||
let(:file) { 'file.exe' }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the extension has an unknown mime type' do
|
||||
context 'if the extension is for a programming language' do
|
||||
let(:file) { 'file.ini' }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the extension is not for a programming language' do
|
||||
let(:file) { 'file.wtf' }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the blob is not stored externally' do
|
||||
context 'if the blob is binary' do
|
||||
let(:blob) { fake_blob(path: 'file.pdf', binary: true, container: container) }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the blob is text-based' do
|
||||
let(:blob) { fake_blob(path: 'file.md', container: container) }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
expect(blob.binary?).to eq(:result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -569,9 +259,7 @@ describe Blob do
|
|||
|
||||
describe '#rich_viewer' do
|
||||
context 'when the blob has an external storage error' do
|
||||
before do
|
||||
project.lfs_enabled = false
|
||||
end
|
||||
let(:lfs_enabled) { false }
|
||||
|
||||
it 'returns nil' do
|
||||
blob = fake_blob(path: 'file.pdf', lfs: true)
|
||||
|
@ -631,9 +319,7 @@ describe Blob do
|
|||
|
||||
describe '#auxiliary_viewer' do
|
||||
context 'when the blob has an external storage error' do
|
||||
before do
|
||||
project.lfs_enabled = false
|
||||
end
|
||||
let(:lfs_enabled) { false }
|
||||
|
||||
it 'returns nil' do
|
||||
blob = fake_blob(path: 'LICENSE', lfs: true)
|
||||
|
@ -676,63 +362,21 @@ describe Blob do
|
|||
end
|
||||
|
||||
describe '#rendered_as_text?' do
|
||||
shared_examples 'returns true' do
|
||||
it do
|
||||
expect(blob.rendered_as_text?(ignore_errors: ignore_errors)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'returns false' do
|
||||
it do
|
||||
expect(blob.rendered_as_text?(ignore_errors: ignore_errors)).to be_falsey
|
||||
end
|
||||
end
|
||||
subject { blob.rendered_as_text?(ignore_errors: ignore_errors) }
|
||||
|
||||
context 'when ignoring errors' do
|
||||
let(:ignore_errors) { true }
|
||||
|
||||
context 'when the simple viewer is text-based' do
|
||||
let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes, container: container) }
|
||||
let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes) }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when the simple viewer is binary' do
|
||||
let(:blob) { fake_blob(path: 'file.pdf', binary: true, size: 100.megabytes, container: container) }
|
||||
let(:blob) { fake_blob(path: 'file.pdf', binary: true, size: 100.megabytes) }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
it { is_expected.to be_falsy }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -740,47 +384,15 @@ describe Blob do
|
|||
let(:ignore_errors) { false }
|
||||
|
||||
context 'when the viewer has render errors' do
|
||||
let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes, container: container) }
|
||||
let(:blob) { fake_blob(path: 'file.md', size: 100.megabytes) }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns false'
|
||||
end
|
||||
it { is_expected.to be_falsy }
|
||||
end
|
||||
|
||||
context "when the viewer doesn't have render errors" do
|
||||
let(:blob) { fake_blob(path: 'file.md', container: container) }
|
||||
let(:blob) { fake_blob(path: 'file.md') }
|
||||
|
||||
context 'with project' do
|
||||
let(:container) { project }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with personal snippet' do
|
||||
let(:container) { personal_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
|
||||
context 'with project snippet' do
|
||||
let(:container) { project_snippet }
|
||||
|
||||
it_behaves_like 'returns true'
|
||||
end
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -270,7 +270,7 @@ describe Ci::Runner do
|
|||
it { is_expected.to eq([@runner2])}
|
||||
end
|
||||
|
||||
describe '#online?' do
|
||||
describe '#online?', :clean_gitlab_redis_cache do
|
||||
let(:runner) { create(:ci_runner, :instance) }
|
||||
|
||||
subject { runner.online? }
|
||||
|
@ -332,7 +332,7 @@ describe Ci::Runner do
|
|||
end
|
||||
|
||||
def stub_redis_runner_contacted_at(value)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
Gitlab::Redis::Cache.with do |redis|
|
||||
cache_key = runner.send(:cache_attribute_key)
|
||||
expect(redis).to receive(:get).with(cache_key)
|
||||
.and_return({ contacted_at: value }.to_json).at_least(:once)
|
||||
|
@ -640,7 +640,7 @@ describe Ci::Runner do
|
|||
end
|
||||
|
||||
def expect_redis_update
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
Gitlab::Redis::Cache.with do |redis|
|
||||
redis_key = runner.send(:cache_attribute_key)
|
||||
expect(redis).to receive(:set).with(redis_key, anything, any_args)
|
||||
end
|
||||
|
@ -664,7 +664,7 @@ describe Ci::Runner do
|
|||
end
|
||||
|
||||
it 'cleans up the queue' do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
Gitlab::Redis::Cache.with do |redis|
|
||||
expect(redis.get(queue_key)).to be_nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -325,3 +325,20 @@ describe Snippet, 'Mentionable' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe PersonalSnippet, 'Mentionable' do
|
||||
describe '#store_mentions!' do
|
||||
it_behaves_like 'mentions in description', :personal_snippet
|
||||
it_behaves_like 'mentions in notes', :personal_snippet do
|
||||
let(:note) { create(:note_on_personal_snippet) }
|
||||
let(:mentionable) { note.noteable }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'load mentions' do
|
||||
it_behaves_like 'load mentions from DB', :personal_snippet do
|
||||
let(:note) { create(:note_on_personal_snippet) }
|
||||
let(:mentionable) { note.noteable }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,7 +31,7 @@ describe RedisCacheable do
|
|||
subject { instance.cached_attribute(payload.each_key.first) }
|
||||
|
||||
it 'gets the cache attribute' do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
Gitlab::Redis::Cache.with do |redis|
|
||||
expect(redis).to receive(:get).with(cache_key)
|
||||
.and_return(payload.to_json)
|
||||
end
|
||||
|
@ -44,7 +44,7 @@ describe RedisCacheable do
|
|||
subject { instance.cache_attributes(payload) }
|
||||
|
||||
it 'sets the cache attributes' do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
Gitlab::Redis::Cache.with do |redis|
|
||||
expect(redis).to receive(:set).with(cache_key, payload.to_json, anything)
|
||||
end
|
||||
|
||||
|
@ -52,7 +52,7 @@ describe RedisCacheable do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#cached_attr_reader', :clean_gitlab_redis_shared_state do
|
||||
describe '#cached_attr_reader', :clean_gitlab_redis_cache do
|
||||
subject { instance.name }
|
||||
|
||||
before do
|
||||
|
|
|
@ -25,6 +25,11 @@ describe Group do
|
|||
it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
|
||||
it { is_expected.to have_many(:container_repositories) }
|
||||
|
||||
it_behaves_like 'model with wiki' do
|
||||
let(:container) { create(:group, :nested, :wiki_repo) }
|
||||
let(:container_without_wiki) { create(:group, :nested) }
|
||||
end
|
||||
|
||||
describe '#members & #requesters' do
|
||||
let(:requester) { create(:user) }
|
||||
let(:developer) { create(:user) }
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe GroupWiki do
|
||||
it_behaves_like 'wiki model' do
|
||||
let(:wiki_container) { create(:group, :wiki_repo) }
|
||||
let(:wiki_container_without_repo) { create(:group) }
|
||||
|
||||
before do
|
||||
wiki_container.add_owner(user)
|
||||
end
|
||||
|
||||
describe '#storage' do
|
||||
it 'uses the group repository prefix' do
|
||||
expect(subject.storage.base_dir).to start_with('@groups/')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#repository_storage' do
|
||||
it 'returns the default storage' do
|
||||
expect(subject.repository_storage).to eq('default')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#hashed_storage?' do
|
||||
it 'returns true' do
|
||||
expect(subject.hashed_storage?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#disk_path' do
|
||||
it 'returns the repository storage path' do
|
||||
expect(subject.disk_path).to eq("#{subject.storage.disk_path}.wiki")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,5 +22,6 @@ describe PersonalSnippet do
|
|||
let(:stubbed_container) { build_stubbed(:personal_snippet) }
|
||||
let(:expected_full_path) { "@snippets/#{container.id}" }
|
||||
let(:expected_web_url_path) { "snippets/#{container.id}" }
|
||||
let(:expected_repo_url_path) { expected_web_url_path }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -38,5 +38,6 @@ describe ProjectSnippet do
|
|||
let(:stubbed_container) { build_stubbed(:project_snippet) }
|
||||
let(:expected_full_path) { "#{container.project.full_path}/@snippets/#{container.id}" }
|
||||
let(:expected_web_url_path) { "#{container.project.full_path}/snippets/#{container.id}" }
|
||||
let(:expected_repo_url_path) { expected_web_url_path }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -118,6 +118,11 @@ describe Project do
|
|||
let(:expected_full_path) { "#{container.namespace.full_path}/somewhere" }
|
||||
end
|
||||
|
||||
it_behaves_like 'model with wiki' do
|
||||
let(:container) { create(:project, :wiki_repo) }
|
||||
let(:container_without_wiki) { create(:project) }
|
||||
end
|
||||
|
||||
it 'has an inverse relationship with merge requests' do
|
||||
expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
|
||||
end
|
||||
|
@ -263,27 +268,6 @@ describe Project do
|
|||
create(:project)
|
||||
end
|
||||
|
||||
describe 'wiki path conflict' do
|
||||
context "when the new path has been used by the wiki of other Project" do
|
||||
it 'has an error on the name attribute' do
|
||||
new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
|
||||
|
||||
expect(new_project).not_to be_valid
|
||||
expect(new_project.errors[:name].first).to eq(_('has already been taken'))
|
||||
end
|
||||
end
|
||||
|
||||
context "when the new wiki path has been used by the path of other Project" do
|
||||
it 'has an error on the name attribute' do
|
||||
project_with_wiki_suffix = create(:project, path: 'foo.wiki')
|
||||
new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
|
||||
|
||||
expect(new_project).not_to be_valid
|
||||
expect(new_project.errors[:name].first).to eq(_('has already been taken'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'repository storages inclusion' do
|
||||
let(:project2) { build(:project, repository_storage: 'missing') }
|
||||
|
||||
|
@ -4716,20 +4700,6 @@ describe Project do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#wiki_repository_exists?' do
|
||||
it 'returns true when the wiki repository exists' do
|
||||
project = create(:project, :wiki_repo)
|
||||
|
||||
expect(project.wiki_repository_exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false when the wiki repository does not exist' do
|
||||
project = create(:project)
|
||||
|
||||
expect(project.wiki_repository_exists?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#write_repository_config' do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
|
|
|
@ -1,448 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
require 'spec_helper'
|
||||
|
||||
describe ProjectWiki do
|
||||
let(:user) { create(:user, :commit_email) }
|
||||
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
|
||||
let(:repository) { project.repository }
|
||||
let(:gitlab_shell) { Gitlab::Shell.new }
|
||||
let(:project_wiki) { described_class.new(project, user) }
|
||||
let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo', 'group/project.wiki') }
|
||||
let(:commit) { project_wiki.repository.head_commit }
|
||||
it_behaves_like 'wiki model' do
|
||||
let(:wiki_container) { create(:project, :wiki_repo, namespace: user.namespace) }
|
||||
let(:wiki_container_without_repo) { create(:project, namespace: user.namespace) }
|
||||
|
||||
subject { project_wiki }
|
||||
it { is_expected.to delegate_method(:storage).to(:container) }
|
||||
it { is_expected.to delegate_method(:repository_storage).to(:container) }
|
||||
it { is_expected.to delegate_method(:hashed_storage?).to(:container) }
|
||||
|
||||
it { is_expected.to delegate_method(:repository_storage).to :project }
|
||||
it { is_expected.to delegate_method(:hashed_storage?).to :project }
|
||||
|
||||
describe "#full_path" do
|
||||
it "returns the project path with namespace with the .wiki extension" do
|
||||
expect(subject.full_path).to eq(project.full_path + '.wiki')
|
||||
end
|
||||
|
||||
it 'returns the same value as #full_path' do
|
||||
expect(subject.full_path).to eq(subject.full_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#web_url' do
|
||||
it 'returns the full web URL to the wiki' do
|
||||
expect(subject.web_url).to eq(Gitlab::UrlBuilder.build(subject))
|
||||
end
|
||||
end
|
||||
|
||||
describe "#url_to_repo" do
|
||||
it "returns the correct ssh url to the repo" do
|
||||
expect(subject.url_to_repo).to eq(Gitlab::RepositoryUrlBuilder.build(subject.repository.full_path, protocol: :ssh))
|
||||
end
|
||||
end
|
||||
|
||||
describe "#ssh_url_to_repo" do
|
||||
it "equals #url_to_repo" do
|
||||
expect(subject.ssh_url_to_repo).to eq(subject.url_to_repo)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#http_url_to_repo" do
|
||||
it "returns the correct http url to the repo" do
|
||||
expect(subject.http_url_to_repo).to eq(Gitlab::RepositoryUrlBuilder.build(subject.repository.full_path, protocol: :http))
|
||||
end
|
||||
end
|
||||
|
||||
describe "#wiki_base_path" do
|
||||
it "returns the wiki base path" do
|
||||
wiki_base_path = "#{Gitlab.config.gitlab.relative_url_root}/#{project.full_path}/-/wikis"
|
||||
|
||||
expect(subject.wiki_base_path).to eq(wiki_base_path)
|
||||
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(project_wiki.create_page("index", "test content")).to be_truthy
|
||||
end
|
||||
|
||||
it "creates a new wiki repo with a default commit message" do
|
||||
expect(project_wiki.create_page("index", "test content", :markdown, "")).to be_truthy
|
||||
|
||||
page = project_wiki.find_page('index')
|
||||
|
||||
expect(page.last_version.message).to eq("#{user.username} created page: index")
|
||||
end
|
||||
|
||||
it "raises CouldNotCreateWikiError if it can't create the wiki repository" do
|
||||
# Create a fresh project which will not have a wiki
|
||||
project_wiki = described_class.new(create(:project), user)
|
||||
expect(project_wiki.repository).to receive(:create_if_not_exists) { false }
|
||||
|
||||
expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#empty?" do
|
||||
context "when the wiki repository is empty" do
|
||||
describe '#empty?' do
|
||||
subject { super().empty? }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
describe '#disk_path' do
|
||||
it 'returns the repository storage path' do
|
||||
expect(subject.disk_path).to eq("#{subject.container.disk_path}.wiki")
|
||||
end
|
||||
end
|
||||
|
||||
context "when the wiki has pages" do
|
||||
before do
|
||||
project_wiki.create_page("index", "This is an awesome new Gollum Wiki")
|
||||
project_wiki.create_page("another-page", "This is another page")
|
||||
end
|
||||
describe '#update_container_activity' do
|
||||
it 'updates project activity' do
|
||||
wiki_container.update!(
|
||||
last_activity_at: nil,
|
||||
last_repository_updated_at: nil
|
||||
)
|
||||
|
||||
describe '#empty?' do
|
||||
subject { super().empty? }
|
||||
subject.create_page('Test Page', 'This is content')
|
||||
wiki_container.reload
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
|
||||
it 'only instantiates a Wiki page once' do
|
||||
expect(WikiPage).to receive(:new).once.and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
expect(wiki_container.last_activity_at).to be_within(1.minute).of(Time.now)
|
||||
expect(wiki_container.last_repository_updated_at).to be_within(1.minute).of(Time.now)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#list_pages" do
|
||||
let(:wiki_pages) { subject.list_pages }
|
||||
|
||||
before do
|
||||
create_page("index", "This is an index")
|
||||
create_page("index2", "This is an index2")
|
||||
create_page("an index3", "This is an index3")
|
||||
end
|
||||
|
||||
after do
|
||||
wiki_pages.each do |wiki_page|
|
||||
destroy_page(wiki_page.page)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns an array of WikiPage instances" do
|
||||
expect(wiki_pages.first).to be_a WikiPage
|
||||
end
|
||||
|
||||
it 'does not load WikiPage content by default' do
|
||||
wiki_pages.each do |page|
|
||||
expect(page.content).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns all pages by default' do
|
||||
expect(wiki_pages.count).to eq(3)
|
||||
end
|
||||
|
||||
context "with limit option" do
|
||||
it 'returns limited set of pages' do
|
||||
expect(subject.list_pages(limit: 1).count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "with sorting options" do
|
||||
it 'returns pages sorted by title by default' do
|
||||
pages = ['an index3', 'index', 'index2']
|
||||
|
||||
expect(subject.list_pages.map(&:title)).to eq(pages)
|
||||
expect(subject.list_pages(direction: "desc").map(&:title)).to eq(pages.reverse)
|
||||
end
|
||||
|
||||
it 'returns pages sorted by created_at' do
|
||||
pages = ['index', 'index2', 'an index3']
|
||||
|
||||
expect(subject.list_pages(sort: 'created_at').map(&:title)).to eq(pages)
|
||||
expect(subject.list_pages(sort: 'created_at', direction: "desc").map(&:title)).to eq(pages.reverse)
|
||||
end
|
||||
end
|
||||
|
||||
context "with load_content option" do
|
||||
let(:pages) { subject.list_pages(load_content: true) }
|
||||
|
||||
it 'loads WikiPage content' do
|
||||
expect(pages.first.content).to eq("This is an index3")
|
||||
expect(pages.second.content).to eq("This is an index")
|
||||
expect(pages.third.content).to eq("This is an index2")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#find_page" do
|
||||
before do
|
||||
create_page("index page", "This is an awesome Gollum Wiki")
|
||||
end
|
||||
|
||||
after do
|
||||
subject.list_pages.each { |page| destroy_page(page.page) }
|
||||
end
|
||||
|
||||
it "returns the latest version of the page if it exists" do
|
||||
page = subject.find_page("index page")
|
||||
expect(page.title).to eq("index page")
|
||||
end
|
||||
|
||||
it "returns nil if the page does not exist" do
|
||||
expect(subject.find_page("non-existent")).to eq(nil)
|
||||
end
|
||||
|
||||
it "can find a page by slug" do
|
||||
page = subject.find_page("index-page")
|
||||
expect(page.title).to eq("index page")
|
||||
end
|
||||
|
||||
it "returns a WikiPage instance" do
|
||||
page = subject.find_page("index page")
|
||||
expect(page).to be_a WikiPage
|
||||
end
|
||||
|
||||
context 'pages with multibyte-character title' do
|
||||
before do
|
||||
create_page("autre pagé", "C'est un génial Gollum Wiki")
|
||||
end
|
||||
|
||||
it "can find a page by slug" do
|
||||
page = subject.find_page("autre pagé")
|
||||
expect(page.title).to eq("autre pagé")
|
||||
end
|
||||
end
|
||||
|
||||
context 'pages with invalidly-encoded content' do
|
||||
before do
|
||||
create_page("encoding is fun", "f\xFCr".b)
|
||||
end
|
||||
|
||||
it "can find the page" do
|
||||
page = subject.find_page("encoding is fun")
|
||||
expect(page.content).to eq("fr")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_sidebar' do
|
||||
before do
|
||||
create_page(described_class::SIDEBAR, 'This is an awesome Sidebar')
|
||||
end
|
||||
|
||||
after do
|
||||
subject.list_pages.each { |page| destroy_page(page.page) }
|
||||
end
|
||||
|
||||
it 'finds the page defined as _sidebar' do
|
||||
page = subject.find_page('_sidebar')
|
||||
|
||||
expect(page.content).to eq('This is an awesome Sidebar')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_file' do
|
||||
let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
|
||||
|
||||
before do
|
||||
subject.wiki # Make sure the wiki repo exists
|
||||
|
||||
repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do
|
||||
subject.repository.path_to_repo
|
||||
end
|
||||
|
||||
BareRepoOperations.new(repo_path).commit_file(image, 'image.png')
|
||||
end
|
||||
|
||||
it 'returns the latest version of the file if it exists' do
|
||||
file = subject.find_file('image.png')
|
||||
expect(file.mime_type).to eq('image/png')
|
||||
end
|
||||
|
||||
it 'returns nil if the page does not exist' do
|
||||
expect(subject.find_file('non-existent')).to eq(nil)
|
||||
end
|
||||
|
||||
it 'returns a Gitlab::Git::WikiFile instance' do
|
||||
file = subject.find_file('image.png')
|
||||
expect(file).to be_a Gitlab::Git::WikiFile
|
||||
end
|
||||
|
||||
it 'returns the whole file' do
|
||||
file = subject.find_file('image.png')
|
||||
image.rewind
|
||||
|
||||
expect(file.raw_data.b).to eq(image.read.b)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#create_page" do
|
||||
after do
|
||||
destroy_page(subject.list_pages.first.page)
|
||||
end
|
||||
|
||||
it "creates a new wiki page" do
|
||||
expect(subject.create_page("test page", "this is content")).not_to eq(false)
|
||||
expect(subject.list_pages.count).to eq(1)
|
||||
end
|
||||
|
||||
it "returns false when a duplicate page exists" do
|
||||
subject.create_page("test page", "content")
|
||||
expect(subject.create_page("test page", "content")).to eq(false)
|
||||
end
|
||||
|
||||
it "stores an error message when a duplicate page exists" do
|
||||
2.times { subject.create_page("test page", "content") }
|
||||
expect(subject.error_message).to match(/Duplicate page:/)
|
||||
end
|
||||
|
||||
it "sets the correct commit message" do
|
||||
subject.create_page("test page", "some content", :markdown, "commit message")
|
||||
expect(subject.list_pages.first.page.version.message).to eq("commit message")
|
||||
end
|
||||
|
||||
it 'sets the correct commit email' do
|
||||
subject.create_page('test page', 'content')
|
||||
|
||||
expect(user.commit_email).not_to eq(user.email)
|
||||
expect(commit.author_email).to eq(user.commit_email)
|
||||
expect(commit.committer_email).to eq(user.commit_email)
|
||||
end
|
||||
|
||||
it 'updates project activity' do
|
||||
subject.create_page('Test Page', 'This is content')
|
||||
|
||||
project.reload
|
||||
|
||||
expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
|
||||
expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_page" do
|
||||
before do
|
||||
create_page("update-page", "some content")
|
||||
@gitlab_git_wiki_page = subject.wiki.page(title: "update-page")
|
||||
subject.update_page(
|
||||
@gitlab_git_wiki_page,
|
||||
content: "some other content",
|
||||
format: :markdown,
|
||||
message: "updated page"
|
||||
)
|
||||
@page = subject.list_pages(load_content: true).first.page
|
||||
end
|
||||
|
||||
after do
|
||||
destroy_page(@page)
|
||||
end
|
||||
|
||||
it "updates the content of the page" do
|
||||
expect(@page.raw_data).to eq("some other content")
|
||||
end
|
||||
|
||||
it "sets the correct commit message" do
|
||||
expect(@page.version.message).to eq("updated page")
|
||||
end
|
||||
|
||||
it 'sets the correct commit email' do
|
||||
expect(user.commit_email).not_to eq(user.email)
|
||||
expect(commit.author_email).to eq(user.commit_email)
|
||||
expect(commit.committer_email).to eq(user.commit_email)
|
||||
end
|
||||
|
||||
it 'updates project activity' do
|
||||
subject.update_page(
|
||||
@gitlab_git_wiki_page,
|
||||
content: 'Yet more content',
|
||||
format: :markdown,
|
||||
message: 'Updated page again'
|
||||
)
|
||||
|
||||
project.reload
|
||||
|
||||
expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
|
||||
expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#delete_page" do
|
||||
before do
|
||||
create_page("index", "some content")
|
||||
@page = subject.wiki.page(title: "index")
|
||||
end
|
||||
|
||||
it "deletes the page" do
|
||||
subject.delete_page(@page)
|
||||
expect(subject.list_pages.count).to eq(0)
|
||||
end
|
||||
|
||||
it 'sets the correct commit email' do
|
||||
subject.delete_page(@page)
|
||||
|
||||
expect(user.commit_email).not_to eq(user.email)
|
||||
expect(commit.author_email).to eq(user.commit_email)
|
||||
expect(commit.committer_email).to eq(user.commit_email)
|
||||
end
|
||||
|
||||
it 'updates project activity' do
|
||||
subject.delete_page(@page)
|
||||
|
||||
project.reload
|
||||
|
||||
expect(project.last_activity_at).to be_within(1.minute).of(Time.now)
|
||||
expect(project.last_repository_updated_at).to be_within(1.minute).of(Time.now)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ensure_repository' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'creates the repository if it not exist' do
|
||||
expect(raw_repository.exists?).to eq(false)
|
||||
|
||||
subject.ensure_repository
|
||||
|
||||
expect(raw_repository.exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'does not create the repository if it exists' do
|
||||
subject.wiki
|
||||
expect(raw_repository.exists?).to eq(true)
|
||||
|
||||
expect(subject).not_to receive(:create_repo!)
|
||||
|
||||
subject.ensure_repository
|
||||
end
|
||||
end
|
||||
|
||||
describe '#hook_attrs' do
|
||||
it 'returns a hash with values' do
|
||||
expect(subject.hook_attrs).to be_a Hash
|
||||
expect(subject.hook_attrs.keys).to contain_exactly(:web_url, :git_ssh_url, :git_http_url, :path_with_namespace, :default_branch)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_temp_repo(path)
|
||||
FileUtils.mkdir_p path
|
||||
system(*%W(#{Gitlab.config.git.bin_path} init --quiet --bare -- #{path}))
|
||||
end
|
||||
|
||||
def remove_temp_repo(path)
|
||||
FileUtils.rm_rf path
|
||||
end
|
||||
|
||||
def commit_details
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.commit_email, "test commit")
|
||||
end
|
||||
|
||||
def create_page(name, content)
|
||||
subject.wiki.write_page(name, :markdown, content, commit_details)
|
||||
end
|
||||
|
||||
def destroy_page(page)
|
||||
subject.delete_page(page, "test commit")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2874,4 +2874,64 @@ describe Repository do
|
|||
expect(repository.submodule_links).to be_a(Gitlab::SubmoduleLinks)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#lfs_enabled?' do
|
||||
let_it_be(:project) { create(:project, :repository, lfs_enabled: true) }
|
||||
|
||||
subject { repository.lfs_enabled? }
|
||||
|
||||
context 'for a project repository' do
|
||||
let(:repository) { project.repository }
|
||||
|
||||
it 'returns true when LFS is enabled' do
|
||||
stub_lfs_setting(enabled: true)
|
||||
|
||||
is_expected.to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when LFS is disabled' do
|
||||
stub_lfs_setting(enabled: false)
|
||||
|
||||
is_expected.to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a project wiki repository' do
|
||||
let(:repository) { project.wiki.repository }
|
||||
|
||||
it 'returns true when LFS is enabled' do
|
||||
stub_lfs_setting(enabled: true)
|
||||
|
||||
is_expected.to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when LFS is disabled' do
|
||||
stub_lfs_setting(enabled: false)
|
||||
|
||||
is_expected.to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a project snippet repository' do
|
||||
let(:snippet) { create(:project_snippet, project: project) }
|
||||
let(:repository) { snippet.repository }
|
||||
|
||||
it 'returns false when LFS is enabled' do
|
||||
stub_lfs_setting(enabled: true)
|
||||
|
||||
is_expected.to be_falsy
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a personal snippet repository' do
|
||||
let(:snippet) { create(:personal_snippet) }
|
||||
let(:repository) { snippet.repository }
|
||||
|
||||
it 'returns false when LFS is enabled' do
|
||||
stub_lfs_setting(enabled: true)
|
||||
|
||||
is_expected.to be_falsy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
require "spec_helper"
|
||||
|
||||
describe WikiPage do
|
||||
let(:project) { create(:project, :wiki_repo) }
|
||||
let(:user) { project.owner }
|
||||
let(:wiki) { ProjectWiki.new(project, user) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:container) { create(:project, :wiki_repo) }
|
||||
let(:wiki) { Wiki.for_container(container, user) }
|
||||
|
||||
let(:new_page) do
|
||||
described_class.new(wiki).tap do |page|
|
||||
|
@ -24,9 +24,9 @@ describe WikiPage do
|
|||
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => false)
|
||||
end
|
||||
|
||||
def enable_front_matter_for_project
|
||||
def enable_front_matter_for(thing)
|
||||
stub_feature_flags(Gitlab::WikiPages::FrontMatterParser::FEATURE_FLAG => {
|
||||
thing: project,
|
||||
thing: thing,
|
||||
enabled: true
|
||||
})
|
||||
end
|
||||
|
@ -114,7 +114,8 @@ describe WikiPage do
|
|||
|
||||
describe '#front_matter' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:wiki_page) { create(:wiki_page, project: project, content: content) }
|
||||
let(:container) { project }
|
||||
let(:wiki_page) { create(:wiki_page, container: container, content: content) }
|
||||
|
||||
shared_examples 'a page without front-matter' do
|
||||
it { expect(wiki_page).to have_attributes(front_matter: {}, content: content) }
|
||||
|
@ -153,12 +154,20 @@ describe WikiPage do
|
|||
|
||||
it_behaves_like 'a page without front-matter'
|
||||
|
||||
context 'but enabled for the project' do
|
||||
context 'but enabled for the container' do
|
||||
before do
|
||||
enable_front_matter_for_project
|
||||
enable_front_matter_for(container)
|
||||
end
|
||||
|
||||
it_behaves_like 'a page with front-matter'
|
||||
context 'with a project container' do
|
||||
it_behaves_like 'a page with front-matter'
|
||||
end
|
||||
|
||||
context 'with a group container' do
|
||||
let(:container) { create(:group) }
|
||||
|
||||
it_behaves_like 'a page with front-matter'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -514,12 +523,20 @@ describe WikiPage do
|
|||
expect([subject, page]).to all(have_attributes(front_matter: be_empty, content: content))
|
||||
end
|
||||
|
||||
context 'but it is enabled for the project' do
|
||||
context 'but it is enabled for the container' do
|
||||
before do
|
||||
enable_front_matter_for_project
|
||||
enable_front_matter_for(container)
|
||||
end
|
||||
|
||||
it_behaves_like 'able to update front-matter'
|
||||
context 'with a project container' do
|
||||
it_behaves_like 'able to update front-matter'
|
||||
end
|
||||
|
||||
context 'with a group container' do
|
||||
let(:container) { create(:group) }
|
||||
|
||||
it_behaves_like 'able to update front-matter'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -812,23 +829,32 @@ describe WikiPage do
|
|||
other_page = create(:wiki_page)
|
||||
|
||||
expect(subject.slug).not_to eq(other_page.slug)
|
||||
expect(subject.project).not_to eq(other_page.project)
|
||||
expect(subject.container).not_to eq(other_page.container)
|
||||
expect(subject).not_to eq(other_page)
|
||||
end
|
||||
|
||||
it 'returns false for page with different slug on same project' do
|
||||
other_page = create(:wiki_page, project: subject.project)
|
||||
it 'returns false for page with different slug on same container' do
|
||||
other_page = create(:wiki_page, container: subject.container)
|
||||
|
||||
expect(subject.slug).not_to eq(other_page.slug)
|
||||
expect(subject.project).to eq(other_page.project)
|
||||
expect(subject.container).to eq(other_page.container)
|
||||
expect(subject).not_to eq(other_page)
|
||||
end
|
||||
|
||||
it 'returns false for page with the same slug on a different project' do
|
||||
it 'returns false for page with the same slug on a different container of the same type' do
|
||||
other_page = create(:wiki_page, title: existing_page.slug)
|
||||
|
||||
expect(subject.slug).to eq(other_page.slug)
|
||||
expect(subject.project).not_to eq(other_page.project)
|
||||
expect(subject.container).not_to eq(other_page.container)
|
||||
expect(subject).not_to eq(other_page)
|
||||
end
|
||||
|
||||
it 'returns false for page with the same slug on a different container type' do
|
||||
group = create(:group, name: container.name)
|
||||
other_page = create(:wiki_page, title: existing_page.slug, container: group)
|
||||
|
||||
expect(subject.slug).to eq(other_page.slug)
|
||||
expect(subject.container).not_to eq(other_page.container)
|
||||
expect(subject).not_to eq(other_page)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,77 +11,110 @@ describe API::Metrics::Dashboard::Annotations do
|
|||
let(:ending_at) { 1.hour.from_now.iso8601 }
|
||||
let(:params) { attributes_for(:metrics_dashboard_annotation, environment: environment, starting_at: starting_at, ending_at: ending_at, dashboard_path: dashboard)}
|
||||
|
||||
describe 'POST /environments/:environment_id/metrics_dashboard/annotations' do
|
||||
before :all do
|
||||
shared_examples 'POST /:source_type/:id/metrics_dashboard/annotations' do |source_type|
|
||||
let(:url) { "/#{source_type.pluralize}/#{source.id}/metrics_dashboard/annotations" }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'feature flag metrics_dashboard_annotations' do
|
||||
context 'is on' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_annotations: { enabled: true, thing: project })
|
||||
end
|
||||
context 'with correct permissions' do
|
||||
context 'with valid parameters' do
|
||||
it 'creates a new annotation', :aggregate_failures do
|
||||
post api("/environments/#{environment.id}/metrics_dashboard/annotations", user), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['environment_id']).to eq(environment.id)
|
||||
expect(json_response['starting_at'].to_time).to eq(starting_at.to_time)
|
||||
expect(json_response['ending_at'].to_time).to eq(ending_at.to_time)
|
||||
expect(json_response['description']).to eq(params[:description])
|
||||
expect(json_response['dashboard_path']).to eq(dashboard)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid parameters' do
|
||||
it 'returns error messsage' do
|
||||
post api("/environments/#{environment.id}/metrics_dashboard/annotations", user),
|
||||
params: { dashboard_path: nil, starting_at: nil, description: nil }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['message']).to include({ "starting_at" => ["can't be blank"], "description" => ["can't be blank"], "dashboard_path" => ["can't be blank"] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with undeclared params' do
|
||||
before do
|
||||
params[:undeclared_param] = 'xyz'
|
||||
end
|
||||
it 'filters out undeclared params' do
|
||||
expect(::Metrics::Dashboard::Annotations::CreateService).to receive(:new).with(user, hash_excluding(:undeclared_param))
|
||||
|
||||
post api("/environments/#{environment.id}/metrics_dashboard/annotations", user), params: params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without correct permissions' do
|
||||
let_it_be(:guest) { create(:user) }
|
||||
|
||||
context "with :source_type == #{source_type.pluralize}" do
|
||||
context 'feature flag metrics_dashboard_annotations' do
|
||||
context 'is on' do
|
||||
before do
|
||||
project.add_guest(guest)
|
||||
stub_feature_flags(metrics_dashboard_annotations: { enabled: true, thing: project })
|
||||
end
|
||||
|
||||
context 'with correct permissions' do
|
||||
context 'with valid parameters' do
|
||||
it 'creates a new annotation', :aggregate_failures do
|
||||
post api(url, user), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response["#{source_type}_id"]).to eq(source.id)
|
||||
expect(json_response['starting_at'].to_time).to eq(starting_at.to_time)
|
||||
expect(json_response['ending_at'].to_time).to eq(ending_at.to_time)
|
||||
expect(json_response['description']).to eq(params[:description])
|
||||
expect(json_response['dashboard_path']).to eq(dashboard)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid parameters' do
|
||||
it 'returns error messsage' do
|
||||
post api(url, user), params: { dashboard_path: nil, starting_at: nil, description: nil }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['message']).to include({ "starting_at" => ["can't be blank"], "description" => ["can't be blank"], "dashboard_path" => ["can't be blank"] })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with undeclared params' do
|
||||
before do
|
||||
params[:undeclared_param] = 'xyz'
|
||||
end
|
||||
|
||||
it 'filters out undeclared params' do
|
||||
expect(::Metrics::Dashboard::Annotations::CreateService).to receive(:new).with(user, hash_excluding(:undeclared_param))
|
||||
|
||||
post api(url, user), params: params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without correct permissions' do
|
||||
let_it_be(:guest) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_guest(guest)
|
||||
end
|
||||
|
||||
it 'returns error messsage' do
|
||||
post api(url, guest), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'is off' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_annotations: { enabled: false })
|
||||
end
|
||||
|
||||
it 'returns error messsage' do
|
||||
post api("/environments/#{environment.id}/metrics_dashboard/annotations", guest), params: params
|
||||
post api(url, user), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
context 'is off' do
|
||||
before do
|
||||
stub_feature_flags(metrics_dashboard_annotations: { enabled: false, thing: project })
|
||||
end
|
||||
|
||||
it 'returns error messsage' do
|
||||
post api("/environments/#{environment.id}/metrics_dashboard/annotations", user), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'environment' do
|
||||
it_behaves_like 'POST /:source_type/:id/metrics_dashboard/annotations', 'environment' do
|
||||
let(:source) { environment }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'group cluster' do
|
||||
it_behaves_like 'POST /:source_type/:id/metrics_dashboard/annotations', 'cluster' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:cluster) { create(:cluster_for_group, groups: [group]) }
|
||||
|
||||
before do
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
let(:source) { cluster }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'project cluster' do
|
||||
it_behaves_like 'POST /:source_type/:id/metrics_dashboard/annotations', 'cluster' do
|
||||
let_it_be(:cluster) { create(:cluster, projects: [project]) }
|
||||
|
||||
let(:source) { cluster }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -252,12 +252,12 @@ describe Snippets::CreateService do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'after_save callback to store_mentions' do
|
||||
shared_examples 'after_save callback to store_mentions' do |mentionable_class|
|
||||
context 'when mentionable attributes change' do
|
||||
let(:extra_opts) { { description: "Description with #{user.to_reference}" } }
|
||||
|
||||
it 'saves mentions' do
|
||||
expect_next_instance_of(Snippet) do |instance|
|
||||
expect_next_instance_of(mentionable_class) do |instance|
|
||||
expect(instance).to receive(:store_mentions!).and_call_original
|
||||
end
|
||||
expect(snippet.user_mentions.count).to eq 1
|
||||
|
@ -266,7 +266,7 @@ describe Snippets::CreateService do
|
|||
|
||||
context 'when mentionable attributes do not change' do
|
||||
it 'does not call store_mentions' do
|
||||
expect_next_instance_of(Snippet) do |instance|
|
||||
expect_next_instance_of(mentionable_class) do |instance|
|
||||
expect(instance).not_to receive(:store_mentions!)
|
||||
end
|
||||
expect(snippet.user_mentions.count).to eq 0
|
||||
|
@ -277,7 +277,7 @@ describe Snippets::CreateService do
|
|||
it 'does not call store_mentions' do
|
||||
base_opts.delete(:title)
|
||||
|
||||
expect_next_instance_of(Snippet) do |instance|
|
||||
expect_next_instance_of(mentionable_class) do |instance|
|
||||
expect(instance).not_to receive(:store_mentions!)
|
||||
end
|
||||
expect(snippet.valid?).to be false
|
||||
|
@ -298,7 +298,7 @@ describe Snippets::CreateService do
|
|||
it_behaves_like 'snippet create data is tracked'
|
||||
it_behaves_like 'an error service response when save fails'
|
||||
it_behaves_like 'creates repository and files'
|
||||
it_behaves_like 'after_save callback to store_mentions'
|
||||
it_behaves_like 'after_save callback to store_mentions', ProjectSnippet
|
||||
end
|
||||
|
||||
context 'when PersonalSnippet' do
|
||||
|
@ -310,9 +310,7 @@ describe Snippets::CreateService do
|
|||
it_behaves_like 'snippet create data is tracked'
|
||||
it_behaves_like 'an error service response when save fails'
|
||||
it_behaves_like 'creates repository and files'
|
||||
pending('See https://gitlab.com/gitlab-org/gitlab/issues/30742') do
|
||||
it_behaves_like 'after_save callback to store_mentions'
|
||||
end
|
||||
it_behaves_like 'after_save callback to store_mentions', PersonalSnippet
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ RSpec.shared_examples 'model with repository' do
|
|||
let(:stubbed_container) { raise NotImplementedError }
|
||||
let(:expected_full_path) { raise NotImplementedError }
|
||||
let(:expected_web_url_path) { expected_full_path }
|
||||
let(:expected_repo_url_path) { expected_full_path }
|
||||
|
||||
describe '#commits_by' do
|
||||
let(:commits) { container.repository.commits('HEAD', limit: 3).commits }
|
||||
|
@ -53,19 +54,19 @@ RSpec.shared_examples 'model with repository' do
|
|||
|
||||
describe '#url_to_repo' do
|
||||
it 'returns the SSH URL to the repository' do
|
||||
expect(container.url_to_repo).to eq("#{Gitlab.config.gitlab_shell.ssh_path_prefix}#{expected_web_url_path}.git")
|
||||
expect(container.url_to_repo).to eq(container.ssh_url_to_repo)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ssh_url_to_repo' do
|
||||
it 'returns the SSH URL to the repository' do
|
||||
expect(container.ssh_url_to_repo).to eq(container.url_to_repo)
|
||||
expect(container.ssh_url_to_repo).to eq("#{Gitlab.config.gitlab_shell.ssh_path_prefix}#{expected_repo_url_path}.git")
|
||||
end
|
||||
end
|
||||
|
||||
describe '#http_url_to_repo' do
|
||||
it 'returns the HTTP URL to the repository' do
|
||||
expect(container.http_url_to_repo).to eq("#{Gitlab.config.gitlab.url}/#{expected_web_url_path}.git")
|
||||
expect(container.http_url_to_repo).to eq("#{Gitlab.config.gitlab.url}/#{expected_repo_url_path}.git")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -95,12 +96,8 @@ RSpec.shared_examples 'model with repository' do
|
|||
end
|
||||
|
||||
context 'when the repo exists' do
|
||||
it { expect(container.empty_repo?).to be(false) }
|
||||
|
||||
it 'returns true when repository is empty' do
|
||||
allow(container.repository).to receive(:empty?).and_return(true)
|
||||
|
||||
expect(container.empty_repo?).to be(true)
|
||||
it 'returns the empty state of the repository' do
|
||||
expect(container.empty_repo?).to be(container.repository.empty?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -146,15 +143,14 @@ RSpec.shared_examples 'model with repository' do
|
|||
end
|
||||
|
||||
it 'picks storage from ApplicationSetting' do
|
||||
expect_next_instance_of(ApplicationSetting) do |instance|
|
||||
expect(instance).to receive(:pick_repository_storage).and_return('picked')
|
||||
end
|
||||
expect(Gitlab::CurrentSettings).to receive(:pick_repository_storage).and_return('picked')
|
||||
|
||||
expect(subject).to eq('picked')
|
||||
end
|
||||
|
||||
it 'picks from the latest available storage', :request_store do
|
||||
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
|
||||
Gitlab::CurrentSettings.expire_current_application_settings
|
||||
Gitlab::CurrentSettings.current_application_settings
|
||||
|
||||
settings = ApplicationSetting.last
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'model with wiki' do
|
||||
describe '#create_wiki' do
|
||||
it 'returns true if the wiki repository already exists' do
|
||||
expect(container.wiki_repository_exists?).to be(true)
|
||||
expect(container.create_wiki).to be(true)
|
||||
end
|
||||
|
||||
it 'returns true if the wiki repository was created' do
|
||||
expect(container_without_wiki.wiki_repository_exists?).to be(false)
|
||||
expect(container_without_wiki.create_wiki).to be(true)
|
||||
expect(container_without_wiki.wiki_repository_exists?).to be(true)
|
||||
end
|
||||
|
||||
context 'when the repository cannot be created' do
|
||||
before do
|
||||
expect(container.wiki).to receive(:wiki) { raise Wiki::CouldNotCreateWikiError }
|
||||
end
|
||||
|
||||
it 'returns false and adds a validation error' do
|
||||
expect(container.create_wiki).to be(false)
|
||||
expect(container.errors[:base]).to contain_exactly('Failed to create wiki')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#wiki_repository_exists?' do
|
||||
it 'returns true when the wiki repository exists' do
|
||||
expect(container.wiki_repository_exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false when the wiki repository does not exist' do
|
||||
expect(container_without_wiki.wiki_repository_exists?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'wiki path conflict' do
|
||||
context 'when the new path has been used by the wiki of other Project' do
|
||||
it 'has an error on the name attribute' do
|
||||
create(:project, namespace: container.parent, path: 'existing')
|
||||
container.path = 'existing.wiki'
|
||||
|
||||
expect(container).not_to be_valid
|
||||
expect(container.errors[:name].first).to eq(_('has already been taken'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the new wiki path has been used by the path of other Project' do
|
||||
it 'has an error on the name attribute' do
|
||||
create(:project, namespace: container.parent, path: 'existing.wiki')
|
||||
container.path = 'existing'
|
||||
|
||||
expect(container).not_to be_valid
|
||||
expect(container.errors[:name].first).to eq(_('has already been taken'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the new path has been used by the wiki of other Group' do
|
||||
it 'has an error on the name attribute' do
|
||||
create(:group, parent: container.parent, path: 'existing')
|
||||
container.path = 'existing.wiki'
|
||||
|
||||
expect(container).not_to be_valid
|
||||
expect(container.errors[:name].first).to eq(_('has already been taken'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the new wiki path has been used by the path of other Group' do
|
||||
it 'has an error on the name attribute' do
|
||||
create(:group, parent: container.parent, path: 'existing.wiki')
|
||||
container.path = 'existing'
|
||||
|
||||
expect(container).not_to be_valid
|
||||
expect(container.errors[:name].first).to eq(_('has already been taken'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -210,6 +210,10 @@ RSpec.shared_examples 'mentions in description' do |mentionable_type|
|
|||
it 'stores no mentions' do
|
||||
expect(mentionable.user_mentions.count).to eq 0
|
||||
end
|
||||
|
||||
it 'renders description_html correctly' do
|
||||
expect(mentionable.description_html).to include("<a href=\"/#{user.username}\" data-user=\"#{user.id}\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,400 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'wiki model' do
|
||||
let_it_be(:user) { create(:user, :commit_email) }
|
||||
let(:wiki_container) { raise NotImplementedError }
|
||||
let(:wiki_container_without_repo) { raise NotImplementedError }
|
||||
let(:wiki) { described_class.new(wiki_container, user) }
|
||||
let(:commit) { subject.repository.head_commit }
|
||||
|
||||
subject { wiki }
|
||||
|
||||
it_behaves_like 'model with repository' do
|
||||
let(:container) { wiki }
|
||||
let(:stubbed_container) { described_class.new(wiki_container_without_repo, user) }
|
||||
let(:expected_full_path) { "#{container.container.full_path}.wiki" }
|
||||
let(:expected_web_url_path) { "#{container.container.web_url(only_path: true).sub(%r{^/}, '')}/-/wikis/home" }
|
||||
end
|
||||
|
||||
describe '#repository' do
|
||||
it 'returns a wiki repository' do
|
||||
expect(subject.repository.repo_type).to be_wiki
|
||||
end
|
||||
end
|
||||
|
||||
describe '#full_path' do
|
||||
it 'returns the container path with the .wiki extension' do
|
||||
expect(subject.full_path).to eq(wiki_container.full_path + '.wiki')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#wiki_base_path' do
|
||||
it 'returns the wiki base path' do
|
||||
expect(subject.wiki_base_path).to eq("#{wiki_container.web_url(only_path: true)}/-/wikis")
|
||||
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
|
||||
expect(subject.empty?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the wiki has pages' do
|
||||
before do
|
||||
subject.create_page('index', 'This is an awesome new Gollum Wiki')
|
||||
subject.create_page('another-page', 'This is another page')
|
||||
end
|
||||
|
||||
describe '#empty?' do
|
||||
it 'returns false' do
|
||||
expect(subject.empty?).to be(false)
|
||||
end
|
||||
|
||||
it 'only instantiates a Wiki page once' do
|
||||
expect(WikiPage).to receive(:new).once.and_call_original
|
||||
|
||||
subject.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#list_pages' do
|
||||
let(:wiki_pages) { subject.list_pages }
|
||||
|
||||
before do
|
||||
create_page('index', 'This is an index')
|
||||
create_page('index2', 'This is an index2')
|
||||
create_page('an index3', 'This is an index3')
|
||||
end
|
||||
|
||||
it 'returns an array of WikiPage instances' do
|
||||
expect(wiki_pages.first).to be_a WikiPage
|
||||
end
|
||||
|
||||
it 'does not load WikiPage content by default' do
|
||||
wiki_pages.each do |page|
|
||||
expect(page.content).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns all pages by default' do
|
||||
expect(wiki_pages.count).to eq(3)
|
||||
end
|
||||
|
||||
context 'with limit option' do
|
||||
it 'returns limited set of pages' do
|
||||
expect(subject.list_pages(limit: 1).count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with sorting options' do
|
||||
it 'returns pages sorted by title by default' do
|
||||
pages = ['an index3', 'index', 'index2']
|
||||
|
||||
expect(subject.list_pages.map(&:title)).to eq(pages)
|
||||
expect(subject.list_pages(direction: 'desc').map(&:title)).to eq(pages.reverse)
|
||||
end
|
||||
|
||||
it 'returns pages sorted by created_at' do
|
||||
pages = ['index', 'index2', 'an index3']
|
||||
|
||||
expect(subject.list_pages(sort: 'created_at').map(&:title)).to eq(pages)
|
||||
expect(subject.list_pages(sort: 'created_at', direction: 'desc').map(&:title)).to eq(pages.reverse)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with load_content option' do
|
||||
let(:pages) { subject.list_pages(load_content: true) }
|
||||
|
||||
it 'loads WikiPage content' do
|
||||
expect(pages.first.content).to eq('This is an index3')
|
||||
expect(pages.second.content).to eq('This is an index')
|
||||
expect(pages.third.content).to eq('This is an index2')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_page' do
|
||||
before do
|
||||
create_page('index page', 'This is an awesome Gollum Wiki')
|
||||
end
|
||||
|
||||
it 'returns the latest version of the page if it exists' do
|
||||
page = subject.find_page('index page')
|
||||
|
||||
expect(page.title).to eq('index page')
|
||||
end
|
||||
|
||||
it 'returns nil if the page does not exist' do
|
||||
expect(subject.find_page('non-existent')).to eq(nil)
|
||||
end
|
||||
|
||||
it 'can find a page by slug' do
|
||||
page = subject.find_page('index-page')
|
||||
|
||||
expect(page.title).to eq('index page')
|
||||
end
|
||||
|
||||
it 'returns a WikiPage instance' do
|
||||
page = subject.find_page('index page')
|
||||
|
||||
expect(page).to be_a WikiPage
|
||||
end
|
||||
|
||||
context 'pages with multibyte-character title' do
|
||||
before do
|
||||
create_page('autre pagé', "C'est un génial Gollum Wiki")
|
||||
end
|
||||
|
||||
it 'can find a page by slug' do
|
||||
page = subject.find_page('autre pagé')
|
||||
|
||||
expect(page.title).to eq('autre pagé')
|
||||
end
|
||||
end
|
||||
|
||||
context 'pages with invalidly-encoded content' do
|
||||
before do
|
||||
create_page('encoding is fun', "f\xFCr".b)
|
||||
end
|
||||
|
||||
it 'can find the page' do
|
||||
page = subject.find_page('encoding is fun')
|
||||
|
||||
expect(page.content).to eq('fr')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_sidebar' do
|
||||
before do
|
||||
create_page(described_class::SIDEBAR, 'This is an awesome Sidebar')
|
||||
end
|
||||
|
||||
it 'finds the page defined as _sidebar' do
|
||||
page = subject.find_page('_sidebar')
|
||||
|
||||
expect(page.content).to eq('This is an awesome Sidebar')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_file' do
|
||||
let(:image) { File.open(Rails.root.join('spec', 'fixtures', 'big-image.png')) }
|
||||
|
||||
before do
|
||||
subject.wiki # Make sure the wiki repo exists
|
||||
|
||||
subject.repository.create_file(user, 'image.png', image, branch_name: subject.default_branch, message: 'add image')
|
||||
end
|
||||
|
||||
it 'returns the latest version of the file if it exists' do
|
||||
file = subject.find_file('image.png')
|
||||
|
||||
expect(file.mime_type).to eq('image/png')
|
||||
end
|
||||
|
||||
it 'returns nil if the page does not exist' do
|
||||
expect(subject.find_file('non-existent')).to eq(nil)
|
||||
end
|
||||
|
||||
it 'returns a Gitlab::Git::WikiFile instance' do
|
||||
file = subject.find_file('image.png')
|
||||
|
||||
expect(file).to be_a Gitlab::Git::WikiFile
|
||||
end
|
||||
|
||||
it 'returns the whole file' do
|
||||
file = subject.find_file('image.png')
|
||||
image.rewind
|
||||
|
||||
expect(file.raw_data.b).to eq(image.read.b)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_page' do
|
||||
it 'creates a new wiki page' do
|
||||
expect(subject.create_page('test page', 'this is content')).not_to eq(false)
|
||||
expect(subject.list_pages.count).to eq(1)
|
||||
end
|
||||
|
||||
it 'returns false when a duplicate page exists' do
|
||||
subject.create_page('test page', 'content')
|
||||
|
||||
expect(subject.create_page('test page', 'content')).to eq(false)
|
||||
end
|
||||
|
||||
it 'stores an error message when a duplicate page exists' do
|
||||
2.times { subject.create_page('test page', 'content') }
|
||||
|
||||
expect(subject.error_message).to match(/Duplicate page:/)
|
||||
end
|
||||
|
||||
it 'sets the correct commit message' do
|
||||
subject.create_page('test page', 'some content', :markdown, 'commit message')
|
||||
|
||||
expect(subject.list_pages.first.page.version.message).to eq('commit message')
|
||||
end
|
||||
|
||||
it 'sets the correct commit email' do
|
||||
subject.create_page('test page', 'content')
|
||||
|
||||
expect(user.commit_email).not_to eq(user.email)
|
||||
expect(commit.author_email).to eq(user.commit_email)
|
||||
expect(commit.committer_email).to eq(user.commit_email)
|
||||
end
|
||||
|
||||
it 'updates container activity' do
|
||||
expect(subject).to receive(:update_container_activity)
|
||||
|
||||
subject.create_page('Test Page', 'This is content')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_page' do
|
||||
before do
|
||||
create_page('update-page', 'some content')
|
||||
@gitlab_git_wiki_page = subject.wiki.page(title: 'update-page')
|
||||
subject.update_page(
|
||||
@gitlab_git_wiki_page,
|
||||
content: 'some other content',
|
||||
format: :markdown,
|
||||
message: 'updated page'
|
||||
)
|
||||
@page = subject.list_pages(load_content: true).first.page
|
||||
end
|
||||
|
||||
it 'updates the content of the page' do
|
||||
expect(@page.raw_data).to eq('some other content')
|
||||
end
|
||||
|
||||
it 'sets the correct commit message' do
|
||||
expect(@page.version.message).to eq('updated page')
|
||||
end
|
||||
|
||||
it 'sets the correct commit email' do
|
||||
expect(user.commit_email).not_to eq(user.email)
|
||||
expect(commit.author_email).to eq(user.commit_email)
|
||||
expect(commit.committer_email).to eq(user.commit_email)
|
||||
end
|
||||
|
||||
it 'updates container activity' do
|
||||
expect(subject).to receive(:update_container_activity)
|
||||
|
||||
subject.update_page(
|
||||
@gitlab_git_wiki_page,
|
||||
content: 'Yet more content',
|
||||
format: :markdown,
|
||||
message: 'Updated page again'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#delete_page' do
|
||||
before do
|
||||
create_page('index', 'some content')
|
||||
@page = subject.wiki.page(title: 'index')
|
||||
end
|
||||
|
||||
it 'deletes the page' do
|
||||
subject.delete_page(@page)
|
||||
|
||||
expect(subject.list_pages.count).to eq(0)
|
||||
end
|
||||
|
||||
it 'sets the correct commit email' do
|
||||
subject.delete_page(@page)
|
||||
|
||||
expect(user.commit_email).not_to eq(user.email)
|
||||
expect(commit.author_email).to eq(user.commit_email)
|
||||
expect(commit.committer_email).to eq(user.commit_email)
|
||||
end
|
||||
|
||||
it 'updates container activity' do
|
||||
expect(subject).to receive(:update_container_activity)
|
||||
|
||||
subject.delete_page(@page)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ensure_repository' do
|
||||
context 'if the repository exists' do
|
||||
it 'does not create the repository' do
|
||||
expect(subject.repository.exists?).to eq(true)
|
||||
expect(subject.repository.raw).not_to receive(:create_repository)
|
||||
|
||||
subject.ensure_repository
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the repository does not exist' do
|
||||
let(:wiki_container) { wiki_container_without_repo }
|
||||
|
||||
it 'creates the repository' do
|
||||
expect(subject.repository.exists?).to eq(false)
|
||||
|
||||
subject.ensure_repository
|
||||
|
||||
expect(subject.repository.exists?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#hook_attrs' do
|
||||
it 'returns a hash with values' do
|
||||
expect(subject.hook_attrs).to be_a Hash
|
||||
expect(subject.hook_attrs.keys).to contain_exactly(:web_url, :git_ssh_url, :git_http_url, :path_with_namespace, :default_branch)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_temp_repo(path)
|
||||
FileUtils.mkdir_p path
|
||||
system(*%W(#{Gitlab.config.git.bin_path} init --quiet --bare -- #{path}))
|
||||
end
|
||||
|
||||
def remove_temp_repo(path)
|
||||
FileUtils.rm_rf path
|
||||
end
|
||||
|
||||
def commit_details
|
||||
Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.commit_email, 'test commit')
|
||||
end
|
||||
|
||||
def create_page(name, content)
|
||||
subject.wiki.write_page(name, :markdown, content, commit_details)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue