Commit 00dff848 authored by Markus Koller's avatar Markus Koller Committed by Douglas Barbosa Alexandre

Use Wiki instance as repository container

Before we started working on group wikis, `ProjectWiki` used a `Project`
instance as the container of its `Repository` instance, so for group
wikis we did the same with `Group`.

This initially made sense and also mostly aligned with the semantics for
`Repository#container`, but while working on wiki diffing [1] we noticed
that the `Commit` and `Blob` classes sometimes use
`container#repository`, which breaks some assumptions and also causes
redundant Gitaly calls to the project repository when diffing wikis.

Refactoring those classes is a lot riskier and would affect other
features too, so in this commit we're instead changing the
`Repository#container` to be the wiki instance, rather than the project
or group. This generally seems to make sense anyway, and only needs some
small adjustments.

To satisfy the interface for `Repository#container`, we need to add
or tweak some methods:

- `Wiki.find_by_id`
- `Wiki#id`
- `Wiki#to_global_id`

We also still need to be able to resolve wiki repositories from their
containers, this is handled in the repository resolver for
`Gitlab::GlRepository::WIKI`.

[1] https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35330
parent 6b19d505
......@@ -71,6 +71,10 @@ module HasRepository
raise NotImplementedError
end
def lfs_enabled?
false
end
def empty_repo?
repository.empty?
end
......
......@@ -35,6 +35,7 @@ class Project < ApplicationRecord
include Integration
include EachBatch
extend Gitlab::Cache::RequestCache
extend Gitlab::Utils::Override
extend Gitlab::ConfigHelper
......@@ -849,6 +850,7 @@ class Project < ApplicationRecord
end
end
override :lfs_enabled?
def lfs_enabled?
return namespace.lfs_enabled? if self[:lfs_enabled].nil?
......
# frozen_string_literal: true
class ProjectWiki < Wiki
self.container_class = Project
alias_method :project, :container
# Project wikis are tied to the main project storage
delegate :storage, :repository_storage, :hashed_storage?, to: :container
delegate :storage, :repository_storage, :hashed_storage?, :lfs_enabled?, to: :container
override :disk_path
def disk_path(*args, &block)
......
......@@ -26,6 +26,7 @@ class Repository
delegate :ref_name_for_sha, to: :raw_repository
delegate :bundle_to_disk, to: :raw_repository
delegate :lfs_enabled?, to: :container
CreateTreeError = Class.new(StandardError)
AmbiguousRefError = Class.new(StandardError)
......@@ -1142,21 +1143,10 @@ class Repository
end
def project
if repo_type.snippet?
container.project
elsif container.is_a?(Project)
container
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?
container
else
false # LFS is not supported for snippet or group repositories
container.try(:project)
end
end
......
......@@ -4,6 +4,7 @@ class Wiki
extend ::Gitlab::Utils::Override
include HasRepository
include Gitlab::Utils::StrongMemoize
include GlobalID::Identification
MARKUPS = { # rubocop:disable Style/MultilineIfModifier
'Markdown' => :markdown,
......@@ -28,14 +29,46 @@ class Wiki
# an operation fails.
attr_reader :error_message
def self.for_container(container, user = nil)
# Support run_after_commit callbacks, since we don't have a DB record
# we delegate to the container.
delegate :run_after_commit, to: :container
class << self
attr_accessor :container_class
def for_container(container, user = nil)
"#{container.class.name}Wiki".constantize.new(container, user)
end
# This is needed to support repository lookup through Gitlab::GlRepository::Identifier
def find_by_id(container_id)
container_class.find_by_id(container_id)&.wiki
end
end
def initialize(container, user = nil)
raise ArgumentError, "user must be a User, got #{user.class}" if user && !user.is_a?(User)
@container = container
@user = user
raise ArgumentError, "user must be a User, got #{user.class}" if user && !user.is_a?(User)
end
def ==(other)
other.is_a?(self.class) && container == other.container
end
# This is needed in:
# - Storage::Hashed
# - Gitlab::GlRepository::RepoType#identifier_for_container
#
# We also need an `#id` to support `build_stubbed` in tests, where the
# value doesn't matter.
#
# NOTE: Wikis don't have a DB record, so this ID can be the same
# for two wikis in different containers and should not be expected to
# be unique. Use `to_global_id` instead if you need a unique ID.
def id
container.id
end
def path
......@@ -183,7 +216,7 @@ class Wiki
override :repository
def repository
@repository ||= Gitlab::GlRepository::WIKI.repository_for(container)
@repository ||= Gitlab::GlRepository::WIKI.repository_for(self)
end
def repository_storage
......@@ -198,7 +231,6 @@ class Wiki
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
......
# frozen_string_literal: true
class WikiPolicy < ::BasePolicy
# Wiki policies are delegated to their container objects (Project or Group)
delegate { subject.container }
end
......@@ -24,7 +24,7 @@ class PostReceive # rubocop:disable Scalability/IdempotentWorker
post_received = Gitlab::GitPostReceive.new(container, identifier, changes, push_options)
if repo_type.wiki?
process_wiki_changes(post_received, container.wiki)
process_wiki_changes(post_received, container)
elsif repo_type.project?
process_project_changes(post_received, container)
elsif repo_type.snippet?
......
......@@ -5,24 +5,24 @@ module EE
include ::ProjectsHelper
include ::ApplicationSettingsHelper
def geo_primary_web_url(project_or_wiki)
File.join(::Gitlab::Geo.primary_node.url, project_or_wiki.full_path)
def geo_primary_web_url(container)
File.join(::Gitlab::Geo.primary_node.url, container.full_path)
end
def geo_primary_ssh_url_to_repo(project_or_wiki)
"#{::Gitlab::Geo.primary_node.clone_url_prefix}#{project_or_wiki.full_path}.git"
def geo_primary_ssh_url_to_repo(container)
"#{::Gitlab::Geo.primary_node.clone_url_prefix}#{container.full_path}.git"
end
def geo_primary_http_url_to_repo(project_or_wiki)
geo_primary_web_url(project_or_wiki) + '.git'
def geo_primary_http_url_to_repo(container)
geo_primary_web_url(container) + '.git'
end
def geo_primary_default_url_to_repo(project_or_wiki)
def geo_primary_default_url_to_repo(container)
case default_clone_protocol
when 'ssh'
geo_primary_ssh_url_to_repo(project_or_wiki)
geo_primary_ssh_url_to_repo(container)
else
geo_primary_http_url_to_repo(project_or_wiki)
geo_primary_http_url_to_repo(container)
end
end
......
# frozen_string_literal: true
class GroupWiki < Wiki
self.container_class = ::Group
alias_method :group, :container
override :create_wiki_repository
......
......@@ -10,12 +10,6 @@ module EE
GEO_SERVER_DOCS_URL = 'https://docs.gitlab.com/ee/administration/geo/replication/using_a_geo_server.html'.freeze
protected
def project_or_wiki
project
end
private
def geo_custom_action
......@@ -67,18 +61,18 @@ module EE
def geo_primary_url_to_repo
case protocol
when 'ssh'
geo_primary_ssh_url_to_repo(project_or_wiki)
geo_primary_ssh_url_to_repo(container)
else
geo_primary_http_url_to_repo(project_or_wiki)
geo_primary_http_url_to_repo(container)
end
end
def primary_http_repo_url
geo_primary_http_url_to_repo(project_or_wiki)
geo_primary_http_url_to_repo(container)
end
def primary_ssh_url_to_repo
geo_primary_ssh_url_to_repo(project_or_wiki)
geo_primary_ssh_url_to_repo(container)
end
def current_replication_lag_message
......
......@@ -25,11 +25,13 @@ module EE
end
def group?
container.is_a?(Group)
# Strict nil check, to avoid any surprises with Object#present?
# which can delegate to #empty?
!group.nil?
end
def group
container if group?
container if container.is_a?(::Group)
end
protected
......
......@@ -12,9 +12,9 @@ module EE
no_group_repo: 'A repository for this group wiki does not exist yet.'
}.freeze
override :project?
def project?
!group?
override :group
def group
container.group if container.is_a?(GroupWiki)
end
override :check_container!
......@@ -53,15 +53,11 @@ module EE
def can_read_group?
if user
user.can?(:read_group, container)
user.can?(:read_group, group)
else
Guest.can?(:read_group, container)
Guest.can?(:read_group, group)
end
end
def project_or_wiki
container.wiki
end
end
end
end
# frozen_string_literal: true
module EE
module Gitlab
module GlRepository
module RepoType
extend ::Gitlab::Utils::Override
override :identifier_for_container
def identifier_for_container(container)
if container.is_a?(GroupWiki)
"group-#{container.id}-#{name}"
else
super
end
end
end
end
end
end
......@@ -5,26 +5,26 @@ require 'spec_helper'
RSpec.describe Gitlab::GitAccessWiki do
include WikiHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :wiki_repo) }
let(:wiki) { create(:project_wiki, project: project) }
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] }
let(:authentication_abilities) { %i[read_project download_code push_code] }
let(:redirected_path) { nil }
let(:access) { described_class.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let(:access) do
described_class.new(user, wiki, 'web',
authentication_abilities: authentication_abilities,
redirected_path: redirected_path)
end
before do
stub_group_wikis(true)
end
describe 'group wiki access' do
let_it_be(:group, reload: true) { create(:group, :private, :wiki_repo) }
let(:access) do
described_class.new(user, group, 'web',
authentication_abilities: authentication_abilities,
redirected_path: redirected_path)
end
let_it_be(:group) { create(:group, :private, :wiki_repo) }
let(:wiki) { create(:group_wiki, group: group) }
describe '#push_access_check' do
subject { access.check('git-receive-pack', changes) }
......@@ -72,7 +72,9 @@ RSpec.describe Gitlab::GitAccessWiki do
end
context 'when the wiki repository does not exist' do
let(:group) { create(:group) }
before do
allow(wiki.repository).to receive(:exists?).and_return(false)
end
it_behaves_like 'not-found git access' do
let(:message) { 'A repository for this group wiki does not exist yet.' }
......
......@@ -3,14 +3,14 @@
require 'spec_helper'
RSpec.describe Gitlab::GlRepository::Identifier do
let_it_be(:group) { create(:group) }
# GitLab Starter feature
context 'group wiki' do
let_it_be(:wiki) { create(:group_wiki) }
it_behaves_like 'parsing gl_repository identifier' do
let(:record_id) { group.id }
let(:record_id) { wiki.group.id }
let(:identifier) { "group-#{record_id}-wiki" }
let(:expected_container) { group }
let(:expected_container) { wiki }
let(:expected_type) { Gitlab::GlRepository::WIKI }
end
end
......
......@@ -5,14 +5,14 @@ require 'spec_helper'
RSpec.describe Gitlab::GlRepository::RepoType do
describe Gitlab::GlRepository::WIKI do
context 'group wiki' do
let_it_be(:group) { create(:group) }
let_it_be(:wiki) { create(:group_wiki) }
it_behaves_like 'a repo type' do
let(:expected_id) { group.id }
let(:expected_id) { wiki.group.id }
let(:expected_identifier) { "group-#{expected_id}-wiki" }
let(:expected_suffix) { '.wiki' }
let(:expected_container) { group }
let(:expected_repository) { ::Repository.new(group.wiki.full_path, group, shard: group.wiki.repository_storage, disk_path: group.wiki.disk_path, repo_type: Gitlab::GlRepository::WIKI) }
let(:expected_container) { wiki }
let(:expected_repository) { ::Repository.new(wiki.full_path, wiki, shard: wiki.repository_storage, disk_path: wiki.disk_path, repo_type: Gitlab::GlRepository::WIKI) }
end
end
end
......
......@@ -8,7 +8,7 @@ RSpec.describe ::Gitlab::GlRepository do
# Group Wiki is a GitLab Starter feature
it 'parses a group wiki gl_repository' do
expect(described_class.parse("group-#{group.id}-wiki")).to eq([group, nil, Gitlab::GlRepository::WIKI])
expect(described_class.parse("group-#{group.id}-wiki")).to eq([group.wiki, nil, Gitlab::GlRepository::WIKI])
end
end
end
......@@ -275,4 +275,16 @@ RSpec.describe Repository do
end
end
end
describe '#lfs_enabled?' do
subject { repository.lfs_enabled? }
context 'for a group wiki repository' do
let(:repository) { build_stubbed(:group_wiki).repository }
it 'returns false' do
is_expected.to be_falsy
end
end
end
end
......@@ -221,7 +221,7 @@ RSpec.describe Geo::RepositoryVerificationPrimaryService do
def stub_wiki_repository(wiki, repository)
allow(Repository).to receive(:new).with(
project.wiki.full_path,
project,
project.wiki,
shard: project.repository_storage,
disk_path: project.wiki.disk_path,
repo_type: Gitlab::GlRepository::WIKI
......
......@@ -100,7 +100,7 @@ RSpec.describe PostReceive do
describe '#process_wiki_changes' do
let(:wiki) { build(:project_wiki, project: project) }
let(:gl_repository) { wiki.repository.repo_type.identifier_for_container(wiki.container) }
let(:gl_repository) { wiki.repository.repo_type.identifier_for_container(wiki) }
it 'calls Git::WikiPushService#execute' do
expect_next_instance_of(::Git::WikiPushService) do |service|
......
......@@ -44,11 +44,7 @@ module Gitlab
end
def url_to_repo
protocol == 'ssh' ? message_subject.ssh_url_to_repo : message_subject.http_url_to_repo
end
def message_subject
repository.repo_type.wiki? ? project.wiki : container
protocol == 'ssh' ? container.ssh_url_to_repo : container.http_url_to_repo
end
end
end
......
......@@ -137,6 +137,10 @@ module Gitlab
private
def check_container!
# Strict nil check, to avoid any surprises with Object#present?
# which can delegate to #empty?
raise NotFoundError, not_found_message if container.nil?
check_project! if project?
end
......@@ -204,9 +208,7 @@ module Gitlab
end
def check_project_accessibility!
if project.blank? || !can_read_project?
raise NotFoundError, not_found_message
end
raise NotFoundError, not_found_message unless can_read_project?
end
def not_found_message
......@@ -279,10 +281,10 @@ module Gitlab
error_message(:download)
end
# We assume that all git-access classes are in project context by default.
# Override this method to be more specific.
def project?
true
# Strict nil check, to avoid any surprises with Object#present?
# which can delegate to #empty?
!project.nil?
end
def project
......@@ -290,7 +292,7 @@ module Gitlab
end
def check_push_access!
if container.repository_read_only?
if project&.repository_read_only?
raise ForbiddenError, error_message(:read_only)
end
......
......@@ -12,7 +12,7 @@ module Gitlab
# 'data' => {
# 'api_endpoints' => %w{geo/proxy_git_ssh/info_refs_receive_pack geo/proxy_git_ssh/receive_pack},
# 'gl_username' => user.username,
# 'primary_repo' => geo_primary_http_url_to_repo(project_or_wiki)
# 'primary_repo' => geo_primary_http_url_to_repo(container)
# }
# }
#
......
......@@ -21,6 +21,11 @@ module Gitlab
@authentication_abilities &= [:download_code, :push_code]
end
override :project
def project
container.project if container.is_a?(ProjectSnippet)
end
override :check
def check(cmd, changes)
check_snippet_accessibility!
......@@ -46,16 +51,6 @@ module Gitlab
# snippets never return custom actions, such as geo replication.
end
override :project?
def project?
project_snippet?
end
override :project
def project
snippet&.project
end
override :check_valid_actor!
def check_valid_actor!
# TODO: Investigate if expanding actor/authentication types are needed.
......@@ -71,10 +66,6 @@ module Gitlab
actor.is_a?(User) || actor.instance_of?(Key)
end
def project_snippet?
snippet.is_a?(ProjectSnippet)
end
override :check_push_access!
def check_push_access!
raise ForbiddenError, ERROR_MESSAGES[:update_snippet] unless user
......
......@@ -12,6 +12,11 @@ module Gitlab
write_to_wiki: "You are not allowed to write to this project's wiki."
}.freeze
override :project
def project
container.project if container.is_a?(ProjectWiki)
end
override :download_ability
def download_ability
:download_wiki_code
......@@ -40,11 +45,6 @@ module Gitlab
def not_found_message
error_message(:not_found)
end
override :repository
def repository
container.wiki.repository
end
end
end
......
......@@ -4,6 +4,8 @@ module Gitlab
class GlRepository
include Singleton
# TODO: Refactor these constants into proper classes
# https://gitlab.com/gitlab-org/gitlab/-/issues/259008
PROJECT = RepoType.new(
name: :project,
access_checker_class: Gitlab::GitAccessProject,
......@@ -12,8 +14,12 @@ module Gitlab
WIKI = RepoType.new(
name: :wiki,
access_checker_class: Gitlab::GitAccessWiki,
repository_resolver: -> (container) { ::Repository.new(container.wiki.full_path, container, shard: container.wiki.repository_storage, disk_path: container.wiki.disk_path, repo_type: WIKI) },
project_resolver: -> (container) { container.is_a?(Project) ? container : nil },
repository_resolver: -> (container) do
wiki = container.is_a?(Wiki) ? container : container.wiki # Also allow passing a Project, Group, or Geo::DeletedProject
::Repository.new(wiki.full_path, wiki, shard: wiki.repository_storage, disk_path: wiki.disk_path, repo_type: WIKI)
end,
container_class: ProjectWiki,
project_resolver: -> (wiki) { wiki.try(:project) },
suffix: :wiki
).freeze
SNIPPET = RepoType.new(
......
......@@ -53,12 +53,13 @@ module Gitlab
private
def container_class
case @container_type
when 'project'
Project
when 'group'
Group
end
# NOTE: This is currently only used and supported for group wikis
# https://gitlab.com/gitlab-org/gitlab/-/issues/219192
return unless @repo_type_name == 'wiki'
"#{@container_type}_#{@repo_type_name}".classify.constantize
rescue NameError
nil
end
end
......
......@@ -29,10 +29,6 @@ module Gitlab
end
def identifier_for_container(container)
if container.is_a?(Group)
return "#{container.class.name.underscore}-#{container.id}-#{name}"
end
"#{name}-#{container.id}"
end
......@@ -84,3 +80,5 @@ module Gitlab
end
end
end
Gitlab::GlRepository::RepoType.prepend_if_ee('EE::Gitlab::GlRepository::RepoType')
......@@ -35,6 +35,10 @@ module Gitlab
snippet, redirected_path = find_snippet(full_path)
[snippet, snippet&.project, redirected_path]
elsif type.wiki?
wiki, redirected_path = find_wiki(full_path)
[wiki, wiki.try(:project), redirected_path]
else
project, redirected_path = find_project(full_path)
......@@ -67,6 +71,17 @@ module Gitlab
[Snippet.find_by_id_and_project(id: snippet_id, project: project), redirected_path]
end
# Wiki path can be either:
# - namespace/project
# - group/subgroup/project
def self.find_wiki(wiki_path)
return [nil, nil] if wiki_path.blank?
project, redirected_path = find_project(wiki_path)
[project&.wiki, redirected_path]
end
def self.extract_snippet_info(snippet_path)
path_segments = snippet_path.split('/')
snippet_id = path_segments.pop
......
......@@ -54,14 +54,18 @@ RSpec.describe WikiHelper do
end
describe '#wiki_attachment_upload_url' do
it 'returns the upload endpoint for project wikis' do
@wiki = build_stubbed(:project_wiki)
let_it_be(:wiki) { build_stubbed(:project_wiki) }
before do
@wiki = wiki
end
it 'returns the upload endpoint for project wikis' do
expect(helper.wiki_attachment_upload_url).to end_with("/api/v4/projects/#{@wiki.project.id}/wikis/attachments")
end
it 'raises an exception for unsupported wiki containers' do
@wiki = Wiki.new(User.new)
allow(wiki).to receive(:container).and_return(User.new)
expect do
helper.wiki_attachment_upload_url
......
......@@ -428,14 +428,12 @@ RSpec.describe Gitlab::GitAccess do
end
context 'when the project repository does not exist' do
it 'returns not found' do
before do
project.add_guest(user)
repo = project.repository
Gitlab::GitalyClient::StorageSettings.allow_disk_access { FileUtils.rm_rf(repo.path) }
# Sanity check for rm_rf
expect(repo.exists?).to eq(false)
allow(project.repository).to receive(:exists?).and_return(false)
end
it 'returns not found' do
expect { pull_access_check }.to raise_error(Gitlab::GitAccess::NotFoundError, 'A repository for this project does not exist yet.')
end
end
......
......@@ -3,17 +3,17 @@
require 'spec_helper'
RSpec.describe Gitlab::GitAccessWiki do
let(:access) { described_class.new(user, project, 'web', authentication_abilities: authentication_abilities, redirected_path: redirected_path) }
let_it_be(:project) { create(:project, :wiki_repo) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :wiki_repo) }
let_it_be(:wiki) { create(:project_wiki, project: project) }
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] }
let(:authentication_abilities) { %i[read_project download_code push_code] }
let(:redirected_path) { nil }
let(:authentication_abilities) do
[
:read_project,
:download_code,
:push_code
]
let(:access) do
described_class.new(user, wiki, 'web',
authentication_abilities: authentication_abilities,
redirected_path: redirected_path)
end
describe '#push_access_check' do
......@@ -64,7 +64,7 @@ RSpec.describe Gitlab::GitAccessWiki do
context 'when the repository does not exist' do
before do
allow(project.wiki).to receive(:repository).and_return(double('Repository', exists?: false))
allow(wiki.repository).to receive(:exists?).and_return(false)
end
it_behaves_like 'not-found git access' do
......
......@@ -35,14 +35,14 @@ RSpec.describe Gitlab::GlRepository::Identifier do
it_behaves_like 'parsing gl_repository identifier' do
let(:record_id) { project.id }
let(:identifier) { "wiki-#{record_id}" }
let(:expected_container) { project }
let(:expected_container) { project.wiki }
let(:expected_type) { Gitlab::GlRepository::WIKI }
end
it_behaves_like 'parsing gl_repository identifier' do
let(:record_id) { project.id }
let(:identifier) { "project-#{record_id}-wiki" }
let(:expected_container) { project }
let(:expected_container) { project.wiki }
let(:expected_type) { Gitlab::GlRepository::WIKI }
end
end
......@@ -87,7 +87,8 @@ RSpec.describe Gitlab::GlRepository::Identifier do
'project-wibble-wiki',
'wiki-1-project',
'snippet',
'project-1-wiki-bar'
'project-1-wiki-bar',
'project-1-project'
]
end
......@@ -96,10 +97,5 @@ RSpec.describe Gitlab::GlRepository::Identifier do
expect { described_class.parse(identifier) }.to raise_error(described_class::InvalidIdentifier)
end
end
it 'raises InvalidIdentifier on project-1-project' do
pending 'https://gitlab.com/gitlab-org/gitlab/-/issues/219192'
expect { described_class.parse('project-1-project') }.to raise_error(described_class::InvalidIdentifier)
end
end
end
......@@ -41,12 +41,14 @@ RSpec.describe Gitlab::GlRepository::RepoType do
end
describe Gitlab::GlRepository::WIKI do
let(:wiki) { project.wiki }
it_behaves_like 'a repo type' do
let(:expected_id) { project.id }
let(:expected_id) { wiki.project.id }
let(:expected_identifier) { "wiki-#{expected_id}" }
let(:expected_suffix) { '.wiki' }
let(:expected_container) { project }
let(:expected_repository) { ::Repository.new(project.wiki.full_path, project, shard: project.wiki.repository_storage, disk_path: project.wiki.disk_path, repo_type: Gitlab::GlRepository::WIKI) }
let(:expected_container) { wiki }
let(:expected_repository) { ::Repository.new(wiki.full_path, wiki, shard: wiki.repository_storage, disk_path: wiki.disk_path, repo_type: Gitlab::GlRepository::WIKI) }
end
it 'knows its type' do
......
......@@ -12,7 +12,7 @@ RSpec.describe ::Gitlab::GlRepository do
end
it 'parses a project wiki gl_repository' do
expect(described_class.parse("wiki-#{project.id}")).to eq([project, project, Gitlab::GlRepository::WIKI])
expect(described_class.parse("wiki-#{project.id}")).to eq([project.wiki, project, Gitlab::GlRepository::WIKI])
end
it 'parses a snippet gl_repository' do
......
......@@ -18,7 +18,7 @@ RSpec.describe ::Gitlab::RepoPath do
end
it 'parses a full wiki project path' do
expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, project, Gitlab::GlRepository::WIKI, nil])
expect(described_class.parse(project.wiki.repository.full_path)).to eq([project.wiki, project, Gitlab::GlRepository::WIKI, nil])
end
it 'parses a personal snippet repository path' do
......@@ -36,7 +36,7 @@ RSpec.describe ::Gitlab::RepoPath do
end
it 'parses a relative wiki path' do
expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, project, Gitlab::GlRepository::WIKI, nil])
expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project.wiki, project, Gitlab::GlRepository::WIKI, nil])
end
it 'parses a relative path starting with /' do
......@@ -49,7 +49,7 @@ RSpec.describe ::Gitlab::RepoPath do
end
it 'parses a relative wiki path' do
expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, project, Gitlab::GlRepository::WIKI, redirect_route])
expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project.wiki, project, Gitlab::GlRepository::WIKI, redirect_route])
end
it 'parses a relative path starting with /' do
......
......@@ -136,6 +136,7 @@ RSpec.describe Project do
let_it_be(:container) { create(:project, :repository, path: 'somewhere') }
let(:stubbed_container) { build_stubbed(:project) }
let(:expected_full_path) { "#{container.namespace.full_path}/somewhere" }
let(:expected_lfs_enabled) { true }
end
it_behaves_like 'model with wiki' do
......@@ -4332,7 +4333,7 @@ RSpec.describe Project do
end
it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the wiki repo is in use' do
Gitlab::ReferenceCounter.new(Gitlab::GlRepository::WIKI.identifier_for_container(project)).increase
Gitlab::ReferenceCounter.new(Gitlab::GlRepository::WIKI.identifier_for_container(project.wiki)).increase
expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in)
......
......@@ -6,6 +6,7 @@ RSpec.describe ProjectWiki do
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) }
let(:wiki_lfs_enabled) { true }
it { is_expected.to delegate_method(:storage).to(:container) }
it { is_expected.to delegate_method(:repository_storage).to(:container) }
......
......@@ -2688,7 +2688,7 @@ RSpec.describe Repository do
expect(subject).to be_a(Gitlab::Git::Repository)
expect(subject.relative_path).to eq(project.disk_path + '.wiki.git')
expect(subject.gl_repository).to eq("wiki-#{project.id}")
expect(subject.gl_project_path).to eq(project.full_path)
expect(subject.gl_project_path).to eq(project.wiki.full_path)
end
end
end
......@@ -2941,12 +2941,19 @@ RSpec.describe Repository do
expect(snippet.repository.project).to be_nil
end
it 'returns the project for a project wiki' do
wiki = create(:project_wiki)
expect(wiki.project).to be(wiki.repository.project)
end
it 'returns the container if it is a project' do
expect(repository.project).to be(project)
end
it 'returns nil if the container is not a project' do
expect(repository).to receive(:container).and_return(Group.new)
repository.container = Group.new
expect(repository.project).to be_nil
end
end
......@@ -2981,17 +2988,11 @@ RSpec.describe Repository do
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)
it 'delegates to the project' do
expect(project).to receive(:lfs_enabled?).and_return(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
......
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Wiki do
describe '.new' do
it 'verifies that the user is a User' do
expect { described_class.new(double, 1) }.to raise_error(ArgumentError)
expect { described_class.new(double, build(:group)) }.to raise_error(ArgumentError)
expect { described_class.new(double, build(:user)) }.not_to raise_error
expect { described_class.new(double, nil) }.not_to raise_error
end
end
end
......@@ -461,7 +461,7 @@ RSpec.describe API::Internal::Base do
end
it_behaves_like 'sets hook env' do
let(:gl_repository) { Gitlab::GlRepository::WIKI.identifier_for_container(project) }
let(:gl_repository) { Gitlab::GlRepository::WIKI.identifier_for_container(project.wiki) }
end
end
......
......@@ -9,7 +9,7 @@ RSpec.describe Repositories::DestroyService do
let(:path) { repository.disk_path }
let(:remove_path) { "#{path}+#{project.id}#{described_class::DELETED_FLAG}" }
subject { described_class.new(project.repository).execute }
subject { described_class.new(repository).execute }
it 'moves the repository to a +deleted folder' do
expect(project.gitlab_shell.repository_exists?(project.repository_storage, path + '.git')).to be_truthy
......@@ -92,4 +92,22 @@ RSpec.describe Repositories::DestroyService do
service.execute
end
end
context 'with a project wiki repository' do
let(:project) { create(:project, :wiki_repo) }
let(:repository) { project.wiki.repository }
it 'schedules the repository deletion' do
subject
expect(Repositories::ShellDestroyService).to receive(:new).with(repository).and_call_original
expect(GitlabShellWorker).to receive(:perform_in)
.with(Repositories::ShellDestroyService::REPO_REMOVAL_DELAY, :remove_repository, project.repository_storage, remove_path)
# Because GitlabShellWorker is inside a run_after_commit callback we need to
# trigger the callback
project.touch
end
end
end
......@@ -4,7 +4,7 @@ module APIInternalBaseHelpers
def gl_repository_for(container)
case container
when ProjectWiki
Gitlab::GlRepository::WIKI.identifier_for_container(container.project)
Gitlab::GlRepository::WIKI.identifier_for_container(container)
when Project
Gitlab::GlRepository::PROJECT.identifier_for_container(container)
when Snippet
......
......@@ -6,6 +6,14 @@ RSpec.shared_examples 'model with repository' do
let(:expected_full_path) { raise NotImplementedError }
let(:expected_web_url_path) { expected_full_path }
let(:expected_repo_url_path) { expected_full_path }
let(:expected_lfs_enabled) { false }
it 'container class includes HasRepository' do
# NOTE: This is not enforced at runtime, since we also need to support Geo::DeletedProject
expect(described_class).to include_module(HasRepository)
expect(container).to be_kind_of(HasRepository)
expect(stubbed_container).to be_kind_of(HasRepository)
end
describe '#commits_by' do
let(:commits) { container.repository.commits('HEAD', limit: 3).commits }
......@@ -74,6 +82,10 @@ RSpec.shared_examples 'model with repository' do
it 'returns valid repo' do
expect(container.repository).to be_kind_of(Repository)
end
it 'uses the same container' do
expect(container.repository.container).to be(container)
end
end
describe '#storage' do
......@@ -88,6 +100,16 @@ RSpec.shared_examples 'model with repository' do
end
end
describe '#lfs_enabled?' do
before do
stub_lfs_setting(enabled: true)
end
it 'returns the expected value' do
expect(container.lfs_enabled?).to eq(expected_lfs_enabled)
end
end
describe '#empty_repo?' do
context 'when the repo does not exist' do
it 'returns true' do
......
......@@ -4,21 +4,99 @@ 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_lfs_enabled) { false }
let(:wiki) { described_class.new(wiki_container, user) }
let(:commit) { subject.repository.head_commit }
subject { wiki }
it 'container class includes HasWiki' do
# NOTE: This is not enforced at runtime, since we also need to support Geo::DeletedProject
expect(wiki_container).to be_kind_of(HasWiki)
expect(wiki_container_without_repo).to be_kind_of(HasWiki)
end
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" }
let(:expected_lfs_enabled) { wiki_lfs_enabled }
end
describe '.container_class' do
it 'is set to the container class' do
expect(described_class.container_class).to eq(wiki_container.class)
end
end
describe '.find_by_id' do
it 'returns a wiki instance if the container is found' do
wiki = described_class.find_by_id(wiki_container.id)
expect(wiki).to be_a(described_class)
expect(wiki.container).to eq(wiki_container)
end
it 'returns nil if the container is not found' do
expect(described_class.find_by_id(-1)).to be_nil
end
end
describe '#initialize' do
it 'accepts a valid user' do
expect do
described_class.new(wiki_container, user)
end.not_to raise_error
end
it 'accepts a blank user' do
expect do
described_class.new(wiki_container, nil)
end.not_to raise_error
end
it 'raises an error for invalid users' do
expect do
described_class.new(wiki_container, Object.new)
end.to raise_error(ArgumentError, 'user must be a User, got Object')
end
end
describe '#run_after_commit' do
it 'delegates to the container' do
expect(wiki_container).to receive(:run_after_commit)
wiki.run_after_commit
end
end
describe '#==' do
it 'returns true for wikis from the same container' do
expect(wiki).to eq(described_class.new(wiki_container))
end
it 'returns false for wikis from different containers' do
expect(wiki).not_to eq(described_class.new(wiki_container_without_repo))
end
end
describe '#id' do
it 'returns the ID of the container' do
expect(wiki.id).to eq(wiki_container.id)
end
end
describe '#to_global_id' do
it 'returns a global ID' do
expect(wiki.to_global_id.to_s).to eq("gid://gitlab/#{wiki.class.name}/#{wiki.id}")
end
end
describe '#repository' do
it 'returns a wiki repository' do
expect(subject.repository.repo_type).to be_wiki
expect(subject.repository.container).to be(subject)
end
end
......
......@@ -281,7 +281,7 @@ RSpec.describe PostReceive do
before do
# Need to mock here so we can expect calls on project
allow(Gitlab::GlRepository).to receive(:parse).and_return([project, project, Gitlab::GlRepository::WIKI])
allow(Gitlab::GlRepository).to receive(:parse).and_return([project.wiki, project, Gitlab::GlRepository::WIKI])
end
it 'updates project activity' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment