Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-19 15:09:33 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-19 15:09:33 +0300
commit652bd073731b0028641672a75355c7918b5ac116 (patch)
treee0239f98153492ac89c6fc374c5dfd1aa270d8bf /app/models
parent2af90cef2e2e9c776eae4394a43dba3be7f33d1e (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/models')
-rw-r--r--app/models/event.rb31
-rw-r--r--app/models/project_wiki.rb9
-rw-r--r--app/models/wiki_page.rb13
-rw-r--r--app/models/wiki_page/meta.rb136
-rw-r--r--app/models/wiki_page/slug.rb26
5 files changed, 210 insertions, 5 deletions
diff --git a/app/models/event.rb b/app/models/event.rb
index 18682bd694c..c4ca5389fdf 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -6,6 +6,7 @@ class Event < ApplicationRecord
include Presentable
include DeleteWithLimit
include CreatedAtFilterable
+ include Gitlab::Utils::StrongMemoize
default_scope { reorder(nil) }
@@ -42,7 +43,8 @@ class Event < ApplicationRecord
note: Note,
project: Project,
snippet: Snippet,
- user: User
+ user: User,
+ wiki: WikiPage::Meta
).freeze
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
@@ -79,6 +81,7 @@ class Event < ApplicationRecord
scope :recent, -> { reorder(id: :desc) }
scope :code_push, -> { where(action: PUSHED) }
scope :merged, -> { where(action: MERGED) }
+ scope :for_wiki_page, -> { where(target_type: WikiPage::Meta.name) }
scope :with_associations, -> do
# We're using preload for "push_event_payload" as otherwise the association
@@ -197,6 +200,14 @@ class Event < ApplicationRecord
created_action? && !target && target_type.nil?
end
+ def created_wiki_page?
+ wiki_page? && action == CREATED
+ end
+
+ def updated_wiki_page?
+ wiki_page? && action == UPDATED
+ end
+
def created_target?
created_action? && target
end
@@ -217,6 +228,10 @@ class Event < ApplicationRecord
target_type == "MergeRequest"
end
+ def wiki_page?
+ target_type == WikiPage::Meta.name
+ end
+
def milestone
target if milestone?
end
@@ -229,6 +244,14 @@ class Event < ApplicationRecord
target if merge_request?
end
+ def wiki_page
+ strong_memoize(:wiki_page) do
+ next unless wiki_page?
+
+ ProjectWiki.new(project, author).find_page(target.canonical_slug)
+ end
+ end
+
def note
target if note?
end
@@ -250,6 +273,10 @@ class Event < ApplicationRecord
'destroyed'
elsif commented_action?
"commented on"
+ elsif created_wiki_page?
+ 'created'
+ elsif updated_wiki_page?
+ 'updated'
elsif created_project_action?
created_project_action_name
else
@@ -362,6 +389,8 @@ class Event < ApplicationRecord
:read_snippet
elsif milestone?
:read_milestone
+ elsif wiki_page?
+ :read_wiki
end
end
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index f8528a41634..50ce1c67453 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -19,10 +19,11 @@ class ProjectWiki
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
- attr_reader :project
def initialize(project, user = nil)
@project = project
@@ -196,9 +197,9 @@ class ProjectWiki
def commit_details(action, message = nil, title = nil)
commit_message = message.presence || default_message(action, title)
- git_user = Gitlab::Git::User.from_gitlab(@user)
+ git_user = Gitlab::Git::User.from_gitlab(user)
- Gitlab::Git::Wiki::CommitDetails.new(@user.id,
+ Gitlab::Git::Wiki::CommitDetails.new(user.id,
git_user.username,
git_user.name,
git_user.email,
@@ -206,7 +207,7 @@ class ProjectWiki
end
def default_message(action, title)
- "#{@user.username} #{action} page: #{title}"
+ "#{user.username} #{action} page: #{title}"
end
def update_project_activity
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 5436f034657..fb65432024e 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -21,6 +21,14 @@ class WikiPage
ActiveModel::Name.new(self, nil, 'wiki')
end
+ def eql?(other)
+ return false unless other.present? && other.is_a?(self.class)
+
+ slug == other.slug && wiki.project == other.wiki.project
+ end
+
+ alias_method :==, :eql?
+
# Sorts and groups pages by directory.
#
# pages - an array of WikiPage objects.
@@ -58,6 +66,7 @@ class WikiPage
# The GitLab ProjectWiki instance.
attr_reader :wiki
+ delegate :project, to: :wiki
# The raw Gitlab::Git::WikiPage instance.
attr_reader :page
@@ -70,6 +79,10 @@ class WikiPage
Gitlab::HookData::WikiPageBuilder.new(self).build
end
+ # Construct a new WikiPage
+ #
+ # @param [ProjectWiki] wiki
+ # @param [Gitlab::Git::WikiPage] page
def initialize(wiki, page = nil)
@wiki = wiki
@page = page
diff --git a/app/models/wiki_page/meta.rb b/app/models/wiki_page/meta.rb
new file mode 100644
index 00000000000..2af7d86ebcc
--- /dev/null
+++ b/app/models/wiki_page/meta.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+class WikiPage
+ class Meta < ApplicationRecord
+ include Gitlab::Utils::StrongMemoize
+
+ CanonicalSlugConflictError = Class.new(ActiveRecord::RecordInvalid)
+
+ self.table_name = 'wiki_page_meta'
+
+ belongs_to :project
+
+ has_many :slugs, class_name: 'WikiPage::Slug', foreign_key: 'wiki_page_meta_id', inverse_of: :wiki_page_meta
+ has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+
+ validates :title, presence: true
+ validates :project_id, presence: true
+ validate :no_two_metarecords_in_same_project_can_have_same_canonical_slug
+
+ scope :with_canonical_slug, ->(slug) do
+ joins(:slugs).where(wiki_page_slugs: { canonical: true, slug: slug })
+ end
+
+ alias_method :resource_parent, :project
+
+ # Return the (updated) WikiPage::Meta record for a given wiki page
+ #
+ # If none is found, then a new record is created, and its fields are set
+ # to reflect the wiki_page passed.
+ #
+ # @param [String] last_known_slug
+ # @param [WikiPage] wiki_page
+ #
+ # As with all `find_or_create` methods, this one raises errors on
+ # validation issues.
+ def self.find_or_create(last_known_slug, wiki_page)
+ project = wiki_page.wiki.project
+ known_slugs = [last_known_slug, wiki_page.slug].compact.uniq
+ raise 'no slugs!' if known_slugs.empty?
+
+ transaction do
+ found = find_by_canonical_slug(known_slugs, project)
+ meta = found || create(title: wiki_page.title, project_id: project.id)
+
+ meta.update_state(found.nil?, known_slugs, wiki_page)
+
+ # We don't need to run validations here, since find_by_canonical_slug
+ # guarantees that there is no conflict in canonical_slug, and DB
+ # constraints on title and project_id enforce our other invariants
+ # This saves us a query.
+ meta
+ end
+ end
+
+ def self.find_by_canonical_slug(canonical_slug, project)
+ meta, conflict = with_canonical_slug(canonical_slug)
+ .where(project_id: project.id)
+ .limit(2)
+
+ if conflict.present?
+ meta.errors.add(:canonical_slug, 'Duplicate value found')
+ raise CanonicalSlugConflictError.new(meta)
+ end
+
+ meta
+ end
+
+ def canonical_slug
+ strong_memoize(:canonical_slug) { slugs.canonical.first&.slug }
+ end
+
+ def canonical_slug=(slug)
+ return if @canonical_slug == slug
+
+ if persisted?
+ transaction do
+ slugs.canonical.update_all(canonical: false)
+ page_slug = slugs.create_with(canonical: true).find_or_create_by(slug: slug)
+ page_slug.update_columns(canonical: true) unless page_slug.canonical?
+ end
+ else
+ slugs.new(slug: slug, canonical: true)
+ end
+
+ @canonical_slug = slug
+ end
+
+ def update_state(created, known_slugs, wiki_page)
+ update_wiki_page_attributes(wiki_page)
+ insert_slugs(known_slugs, created, wiki_page.slug)
+ self.canonical_slug = wiki_page.slug
+ end
+
+ def update_columns(attrs = {})
+ super(attrs.reverse_merge(updated_at: Time.now.utc))
+ end
+
+ def self.update_all(attrs = {})
+ super(attrs.reverse_merge(updated_at: Time.now.utc))
+ end
+
+ private
+
+ def update_wiki_page_attributes(page)
+ update_columns(title: page.title) unless page.title == title
+ end
+
+ def insert_slugs(strings, is_new, canonical_slug)
+ creation = Time.now.utc
+
+ slug_attrs = strings.map do |slug|
+ {
+ wiki_page_meta_id: id,
+ slug: slug,
+ canonical: (is_new && slug == canonical_slug),
+ created_at: creation,
+ updated_at: creation
+ }
+ end
+ slugs.insert_all(slug_attrs) unless !is_new && slug_attrs.size == 1
+
+ @canonical_slug = canonical_slug if is_new || strings.size == 1
+ end
+
+ def no_two_metarecords_in_same_project_can_have_same_canonical_slug
+ return unless project_id.present? && canonical_slug.present?
+
+ offending = self.class.with_canonical_slug(canonical_slug).where(project_id: project_id)
+ offending = offending.where.not(id: id) if persisted?
+
+ if offending.exists?
+ errors.add(:canonical_slug, 'each page in a wiki must have a distinct canonical slug')
+ end
+ end
+ end
+end
diff --git a/app/models/wiki_page/slug.rb b/app/models/wiki_page/slug.rb
new file mode 100644
index 00000000000..246fa8d6442
--- /dev/null
+++ b/app/models/wiki_page/slug.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class WikiPage
+ class Slug < ApplicationRecord
+ self.table_name = 'wiki_page_slugs'
+
+ belongs_to :wiki_page_meta, class_name: 'WikiPage::Meta', inverse_of: :slugs
+
+ validates :slug, presence: true, uniqueness: { scope: :wiki_page_meta_id }
+ validates :canonical, uniqueness: {
+ scope: :wiki_page_meta_id,
+ if: :canonical?,
+ message: 'Only one slug can be canonical per wiki metadata record'
+ }
+
+ scope :canonical, -> { where(canonical: true) }
+
+ def update_columns(attrs = {})
+ super(attrs.reverse_merge(updated_at: Time.now.utc))
+ end
+
+ def self.update_all(attrs = {})
+ super(attrs.reverse_merge(updated_at: Time.now.utc))
+ end
+ end
+end