summaryrefslogtreecommitdiff
path: root/app/finders
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 13:18:24 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-20 13:18:24 +0000
commit0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch)
tree4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /app/finders
parent744144d28e3e7fddc117924fef88de5d9674fe4c (diff)
downloadgitlab-ce-0653e08efd039a5905f3fa4f6e9cef9f5d2f799c.tar.gz
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'app/finders')
-rw-r--r--app/finders/branches_finder.rb4
-rw-r--r--app/finders/ci/pipelines_finder.rb15
-rw-r--r--app/finders/ci/pipelines_for_merge_request_finder.rb24
-rw-r--r--app/finders/ci/runners_finder.rb18
-rw-r--r--app/finders/error_tracking/errors_finder.rb14
-rw-r--r--app/finders/groups/user_groups_finder.rb60
-rw-r--r--app/finders/issuable_finder.rb63
-rw-r--r--app/finders/issuable_finder/params.rb32
-rw-r--r--app/finders/issuables/label_filter.rb155
-rw-r--r--app/finders/issues_finder.rb7
-rw-r--r--app/finders/issues_finder/params.rb4
-rw-r--r--app/finders/packages/helm/package_files_finder.rb2
-rw-r--r--app/finders/packages/helm/packages_finder.rb30
-rw-r--r--app/finders/packages/npm/package_finder.rb17
-rw-r--r--app/finders/projects_finder.rb10
-rw-r--r--app/finders/repositories/tree_finder.rb61
16 files changed, 402 insertions, 114 deletions
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
index 157c454183a..a62d47071d4 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -15,6 +15,10 @@ class BranchesFinder < GitRefsFinder
end
end
+ def total
+ repository.branch_count
+ end
+
private
def names
diff --git a/app/finders/ci/pipelines_finder.rb b/app/finders/ci/pipelines_finder.rb
index a79840216da..39355853d88 100644
--- a/app/finders/ci/pipelines_finder.rb
+++ b/app/finders/ci/pipelines_finder.rb
@@ -25,12 +25,10 @@ module Ci
items = by_status(items)
items = by_ref(items)
items = by_sha(items)
- items = by_name(items)
items = by_username(items)
items = by_yaml_errors(items)
items = by_updated_at(items)
-
- items = by_source(items) if Feature.enabled?(:pipeline_source_filter, project, default_enabled: :yaml)
+ items = by_source(items)
sort_items(items)
end
@@ -116,17 +114,6 @@ module Ci
end
# rubocop: enable CodeReuse/ActiveRecord
- # This method is deprecated and will be removed in 14.3
- # rubocop: disable CodeReuse/ActiveRecord
- def by_name(items)
- if params[:name].present?
- items.joins(:user).where(users: { name: params[:name] })
- else
- items
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
# rubocop: disable CodeReuse/ActiveRecord
def by_username(items)
return items unless params[:username].present?
diff --git a/app/finders/ci/pipelines_for_merge_request_finder.rb b/app/finders/ci/pipelines_for_merge_request_finder.rb
index f769da03738..5d794c0903a 100644
--- a/app/finders/ci/pipelines_for_merge_request_finder.rb
+++ b/app/finders/ci/pipelines_for_merge_request_finder.rb
@@ -29,17 +29,19 @@ module Ci
# Fetch all pipelines without permission check.
def all
- strong_memoize(:all_pipelines) do
- next Ci::Pipeline.none unless source_project
-
- pipelines =
- if merge_request.persisted?
- pipelines_using_cte
- else
- triggered_for_branch.for_sha(commit_shas)
- end
-
- sort(pipelines)
+ ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336891') do
+ strong_memoize(:all_pipelines) do
+ next Ci::Pipeline.none unless source_project
+
+ pipelines =
+ if merge_request.persisted?
+ pipelines_using_cte
+ else
+ triggered_for_branch.for_sha(commit_shas)
+ end
+
+ sort(pipelines)
+ end
end
end
diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb
index d34b3202433..8bc2a47a024 100644
--- a/app/finders/ci/runners_finder.rb
+++ b/app/finders/ci/runners_finder.rb
@@ -7,9 +7,9 @@ module Ci
ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze
DEFAULT_SORT = 'created_at_desc'
- def initialize(current_user:, group: nil, params:)
+ def initialize(current_user:, params:)
@params = params
- @group = group
+ @group = params.delete(:group)
@current_user = current_user
end
@@ -48,10 +48,16 @@ module Ci
def group_runners
raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :admin_group, @group)
- # Getting all runners from the group itself and all its descendants
- descendant_projects = Project.for_group_and_its_subgroups(@group)
-
- @runners = Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects)
+ @runners = case @params[:membership]
+ when :direct
+ Ci::Runner.belonging_to_group(@group.id)
+ when :descendants, nil
+ # Getting all runners from the group itself and all its descendant groups/projects
+ descendant_projects = Project.for_group_and_its_subgroups(@group)
+ Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects)
+ else
+ raise ArgumentError, 'Invalid membership filter'
+ end
end
def filter_by_status!
diff --git a/app/finders/error_tracking/errors_finder.rb b/app/finders/error_tracking/errors_finder.rb
index fb2d4b14dfa..d83a0c487e6 100644
--- a/app/finders/error_tracking/errors_finder.rb
+++ b/app/finders/error_tracking/errors_finder.rb
@@ -13,9 +13,10 @@ module ErrorTracking
collection = project.error_tracking_errors
collection = by_status(collection)
+ collection = sort(collection)
- # Limit collection until pagination implemented
- collection.limit(20)
+ # Limit collection until pagination implemented.
+ limit(collection)
end
private
@@ -33,5 +34,14 @@ module ErrorTracking
def authorized?
Ability.allowed?(current_user, :read_sentry_issue, project)
end
+
+ def sort(collection)
+ params[:sort] ? collection.sort_by_attribute(params[:sort]) : collection.order_id_desc
+ end
+
+ def limit(collection)
+ # Restrict the maximum limit at 100 records.
+ collection.limit([(params[:limit] || 20).to_i, 100].min)
+ end
end
end
diff --git a/app/finders/groups/user_groups_finder.rb b/app/finders/groups/user_groups_finder.rb
new file mode 100644
index 00000000000..5946e3a8933
--- /dev/null
+++ b/app/finders/groups/user_groups_finder.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+# Groups::UserGroupsFinder
+#
+# Used to filter Groups where a user is member
+#
+# Arguments:
+# current_user - user requesting group info on target user
+# target_user - user for which groups will be found
+# params:
+# permissions: string (see Types::Groups::UserPermissionsEnum)
+# search: string used for search on path and group name
+#
+# Initially created to filter user groups and descendants where the user can create projects
+module Groups
+ class UserGroupsFinder
+ def initialize(current_user, target_user, params = {})
+ @current_user = current_user
+ @target_user = target_user
+ @params = params
+ end
+
+ def execute
+ return Group.none unless current_user&.can?(:read_user_groups, target_user)
+ return Group.none if target_user.blank?
+
+ items = by_permission_scope
+ items = by_search(items)
+
+ sort(items)
+ end
+
+ private
+
+ attr_reader :current_user, :target_user, :params
+
+ def sort(items)
+ items.order(path: :asc, id: :asc) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def by_search(items)
+ return items if params[:search].blank?
+
+ items.search(params[:search])
+ end
+
+ def by_permission_scope
+ if permission_scope_create_projects?
+ target_user.manageable_groups(include_groups_with_developer_maintainer_access: true)
+ else
+ target_user.groups
+ end
+ end
+
+ def permission_scope_create_projects?
+ params[:permission_scope] == :create_projects &&
+ Feature.enabled?(:paginatable_namespace_drop_down_for_project_creation, current_user, default_enabled: :yaml)
+ end
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 9f3ca385d93..cf706a8f98e 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -41,7 +41,6 @@ class IssuableFinder
include FinderMethods
include CreatedAtFilter
include Gitlab::Utils::StrongMemoize
- prepend OptimizedIssuableLabelFilter
requires_cross_project_access unless: -> { params.project? }
@@ -149,7 +148,6 @@ class IssuableFinder
# Negates all params found in `negatable_params`
def filter_negated_items(items)
- items = by_negated_label(items)
items = by_negated_milestone(items)
items = by_negated_release(items)
items = by_negated_my_reaction_emoji(items)
@@ -172,29 +170,19 @@ class IssuableFinder
count_params = params.merge(state: nil, sort: nil, force_cte: true)
finder = self.class.new(current_user, count_params)
+ state_counts = finder
+ .execute
+ .reorder(nil)
+ .group(:state_id)
+ .count
+
counts = Hash.new(0)
- # Searching by label includes a GROUP BY in the query, but ours will be last
- # because it is added last. Searching by multiple labels also includes a row
- # per issuable, so we have to count those in Ruby - which is bad, but still
- # better than performing multiple queries.
- #
- # This does not apply when we are using a CTE for the search, as the labels
- # GROUP BY is inside the subquery in that case, so we set labels_count to 1.
- #
- # Groups and projects have separate feature flags to suggest the use
- # of a CTE. The CTE will not be used if the sort doesn't support it,
- # but will always be used for the counts here as we ignore sorting
- # anyway.
- labels_count = params.label_names.any? ? params.label_names.count : 1
- labels_count = 1 if use_cte_for_search?
-
- finder.execute.reorder(nil).group(:state_id).count.each do |key, value|
- counts[count_key(key)] += value / labels_count
+ state_counts.each do |key, value|
+ counts[count_key(key)] += value
end
counts[:all] = counts.values.sum
-
counts.with_indifferent_access
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -332,6 +320,7 @@ class IssuableFinder
def by_search(items)
return items unless search
return items if items.is_a?(ActiveRecord::NullRelation)
+ return items if Feature.enabled?(:disable_anonymous_search, type: :ops) && current_user.nil?
if use_cte_for_search?
cte = Gitlab::SQL::CTE.new(klass.table_name, items)
@@ -359,7 +348,7 @@ class IssuableFinder
def sort(items)
# Ensure we always have an explicit sort order (instead of inheriting
# multiple orders when combining ActiveRecord::Relation objects).
- params[:sort] ? items.sort_by_attribute(params[:sort], excluded_labels: params.label_names) : items.reorder(id: :desc)
+ params[:sort] ? items.sort_by_attribute(params[:sort], excluded_labels: label_filter.label_names_excluded_from_priority_sort) : items.reorder(id: :desc)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -383,6 +372,20 @@ class IssuableFinder
end
end
+ def by_label(items)
+ label_filter.filter(items)
+ end
+
+ def label_filter
+ strong_memoize(:label_filter) do
+ Issuables::LabelFilter.new(
+ params: original_params,
+ project: params.project,
+ group: params.group
+ )
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def by_milestone(items)
return items unless params.milestones?
@@ -435,24 +438,6 @@ class IssuableFinder
items.without_particular_release(not_params[:release_tag], not_params[:project_id])
end
- def by_label(items)
- return items unless params.labels?
-
- if params.filter_by_no_label?
- items.without_label
- elsif params.filter_by_any_label?
- items.any_label(params[:sort])
- else
- items.with_label(params.label_names, params[:sort])
- end
- end
-
- def by_negated_label(items)
- return items unless not_params.labels?
-
- items.without_particular_labels(not_params.label_names)
- end
-
def by_my_reaction_emoji(items)
return items unless params[:my_reaction_emoji] && current_user
diff --git a/app/finders/issuable_finder/params.rb b/app/finders/issuable_finder/params.rb
index 595f4e4cf8a..359a56bd39b 100644
--- a/app/finders/issuable_finder/params.rb
+++ b/app/finders/issuable_finder/params.rb
@@ -29,20 +29,6 @@ class IssuableFinder
params.present?
end
- def filter_by_no_label?
- downcased = label_names.map(&:downcase)
-
- downcased.include?(FILTER_NONE)
- end
-
- def filter_by_any_label?
- label_names.map(&:downcase).include?(FILTER_ANY)
- end
-
- def labels?
- params[:label_name].present?
- end
-
def milestones?
params[:milestone_title].present? || params[:milestone_wildcard_id].present?
end
@@ -160,24 +146,6 @@ class IssuableFinder
end
end
- def label_names
- if labels?
- params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
- else
- []
- end
- end
-
- def labels
- strong_memoize(:labels) do
- if labels? && !filter_by_no_label?
- LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute(skip_authorization: true) # rubocop: disable CodeReuse/Finder
- else
- Label.none
- end
- end
- end
-
def milestones
strong_memoize(:milestones) do
if milestones?
diff --git a/app/finders/issuables/label_filter.rb b/app/finders/issuables/label_filter.rb
new file mode 100644
index 00000000000..2bbc963aa90
--- /dev/null
+++ b/app/finders/issuables/label_filter.rb
@@ -0,0 +1,155 @@
+# frozen_string_literal: true
+
+module Issuables
+ class LabelFilter < BaseFilter
+ include Gitlab::Utils::StrongMemoize
+ extend Gitlab::Cache::RequestCache
+
+ def initialize(project:, group:, **kwargs)
+ @project = project
+ @group = group
+
+ super(**kwargs)
+ end
+
+ def filter(issuables)
+ filtered = by_label(issuables)
+ by_negated_label(filtered)
+ end
+
+ def label_names_excluded_from_priority_sort
+ label_names_from_params
+ end
+
+ private
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def by_label(issuables)
+ return issuables unless label_names_from_params.present?
+
+ target_model = issuables.model
+
+ if filter_by_no_label?
+ issuables.where(label_link_query(target_model).arel.exists.not)
+ elsif filter_by_any_label?
+ issuables.where(label_link_query(target_model).arel.exists)
+ else
+ issuables_with_selected_labels(issuables, label_names_from_params)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def by_negated_label(issuables)
+ return issuables unless label_names_from_not_params.present?
+
+ issuables_without_selected_labels(issuables, label_names_from_not_params)
+ end
+
+ def filter_by_no_label?
+ label_names_from_params.map(&:downcase).include?(FILTER_NONE)
+ end
+
+ def filter_by_any_label?
+ label_names_from_params.map(&:downcase).include?(FILTER_ANY)
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def issuables_with_selected_labels(issuables, label_names)
+ target_model = issuables.model
+
+ if root_namespace
+ all_label_ids = find_label_ids(label_names)
+ # Found less labels in the DB than we were searching for. Return nothing.
+ return issuables.none if all_label_ids.size != label_names.size
+
+ all_label_ids.each do |label_ids|
+ issuables = issuables.where(label_link_query(target_model, label_ids: label_ids).arel.exists)
+ end
+ else
+ label_names.each do |label_name|
+ issuables = issuables.where(label_link_query(target_model, label_names: label_name).arel.exists)
+ end
+ end
+
+ issuables
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def issuables_without_selected_labels(issuables, label_names)
+ target_model = issuables.model
+
+ if root_namespace
+ label_ids = find_label_ids(label_names).flatten(1)
+
+ issuables.where(label_link_query(target_model, label_ids: label_ids).arel.exists.not)
+ else
+ issuables.where(label_link_query(target_model, label_names: label_names).arel.exists.not)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def find_label_ids(label_names)
+ group_labels = Label
+ .where(project_id: nil)
+ .where(title: label_names)
+ .where(group_id: root_namespace.self_and_descendant_ids)
+
+ project_labels = Label
+ .where(group_id: nil)
+ .where(title: label_names)
+ .where(project_id: Project.select(:id).where(namespace_id: root_namespace.self_and_descendant_ids))
+
+ Label
+ .from_union([group_labels, project_labels], remove_duplicates: false)
+ .reorder(nil)
+ .pluck(:title, :id)
+ .group_by(&:first)
+ .values
+ .map { |labels| labels.map(&:last) }
+ end
+ # Avoid repeating label queries times when the finder is instantiated multiple times during the request.
+ request_cache(:find_label_ids) { root_namespace.id }
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def label_link_query(target_model, label_ids: nil, label_names: nil)
+ relation = LabelLink
+ .where(target_type: target_model.name)
+ .where(LabelLink.arel_table['target_id'].eq(target_model.arel_table['id']))
+
+ relation = relation.where(label_id: label_ids) if label_ids
+ relation = relation.joins(:label).where(labels: { name: label_names }) if label_names
+
+ relation
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def label_names_from_params
+ return if params[:label_name].blank?
+
+ strong_memoize(:label_names_from_params) do
+ split_label_names(params[:label_name])
+ end
+ end
+
+ def label_names_from_not_params
+ return if not_params.blank? || not_params[:label_name].blank?
+
+ strong_memoize(:label_names_from_not_params) do
+ split_label_names(not_params[:label_name])
+ end
+ end
+
+ def split_label_names(label_name_param)
+ label_name_param.is_a?(String) ? label_name_param.split(',') : label_name_param
+ end
+
+ def root_namespace
+ strong_memoize(:root_namespace) do
+ (@project || @group)&.root_ancestor
+ end
+ end
+ end
+end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 7595b1c7a15..abf0c180d6b 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -20,6 +20,7 @@
# sort: string
# my_reaction_emoji: string
# public_only: boolean
+# include_hidden: boolean
# due_date: date or '0', '', 'overdue', 'week', or 'month'
# created_after: datetime
# created_before: datetime
@@ -47,8 +48,6 @@ class IssuesFinder < IssuableFinder
# rubocop: disable CodeReuse/ActiveRecord
def with_confidentiality_access_check
- return Issue.all if params.user_can_see_all_issues?
-
# Only admins can see hidden issues, so for non-admins, we filter out any hidden issues
issues = Issue.without_hidden
@@ -76,7 +75,9 @@ class IssuesFinder < IssuableFinder
private
def init_collection
- if params.public_only?
+ if params.include_hidden?
+ Issue.all
+ elsif params.public_only?
Issue.public_only
else
with_confidentiality_access_check
diff --git a/app/finders/issues_finder/params.rb b/app/finders/issues_finder/params.rb
index 2edd8a6f099..02b89f08f9e 100644
--- a/app/finders/issues_finder/params.rb
+++ b/app/finders/issues_finder/params.rb
@@ -6,6 +6,10 @@ class IssuesFinder
params.fetch(:public_only, false)
end
+ def include_hidden?
+ user_can_see_all_issues?
+ end
+
def filter_by_no_due_date?
due_date? && params[:due_date] == Issue::NoDueDate.name
end
diff --git a/app/finders/packages/helm/package_files_finder.rb b/app/finders/packages/helm/package_files_finder.rb
index ba400b27554..c6504d09dce 100644
--- a/app/finders/packages/helm/package_files_finder.rb
+++ b/app/finders/packages/helm/package_files_finder.rb
@@ -6,6 +6,8 @@ module Packages
DEFAULT_PACKAGE_FILES_COUNT = 20
MAX_PACKAGE_FILES_COUNT = 1000
+ delegate :most_recent!, to: :execute
+
def initialize(project, channel, params = {})
@project = project
@channel = channel
diff --git a/app/finders/packages/helm/packages_finder.rb b/app/finders/packages/helm/packages_finder.rb
new file mode 100644
index 00000000000..c58d9292e9f
--- /dev/null
+++ b/app/finders/packages/helm/packages_finder.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Packages
+ module Helm
+ class PackagesFinder
+ include ::Packages::FinderHelper
+
+ MAX_PACKAGES_COUNT = 300
+
+ def initialize(project, channel)
+ @project = project
+ @channel = channel
+ end
+
+ def execute
+ if @channel.blank? || @project.blank?
+ return ::Packages::Package.none
+ end
+
+ pkg_files = ::Packages::PackageFile.for_helm_with_channel(@project, @channel)
+
+ # we use a subquery to get unique packages and at the same time
+ # order + limit them.
+ ::Packages::Package
+ .limit_recent(MAX_PACKAGES_COUNT)
+ .id_in(pkg_files.select(:package_id))
+ end
+ end
+ end
+end
diff --git a/app/finders/packages/npm/package_finder.rb b/app/finders/packages/npm/package_finder.rb
index 92ceac297ee..a367fda37de 100644
--- a/app/finders/packages/npm/package_finder.rb
+++ b/app/finders/packages/npm/package_finder.rb
@@ -5,18 +5,23 @@ module Packages
delegate :find_by_version, to: :execute
delegate :last, to: :execute
- def initialize(package_name, project: nil, namespace: nil)
+ # /!\ CAUTION: don't use last_of_each_version: false with find_by_version. Ordering is not
+ # guaranteed!
+ def initialize(package_name, project: nil, namespace: nil, last_of_each_version: true)
@package_name = package_name
@project = project
@namespace = namespace
+ @last_of_each_version = last_of_each_version
end
def execute
- base.npm
- .with_name(@package_name)
- .installable
- .last_of_each_version
- .preload_files
+ result = base.npm
+ .with_name(@package_name)
+ .installable
+
+ return result unless @last_of_each_version
+
+ result.last_of_each_version
end
private
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index dca3d12f3c9..5537058cc79 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -180,12 +180,20 @@ class ProjectsFinder < UnionFinder
# rubocop: enable CodeReuse/ActiveRecord
def by_topics(items)
- params[:topic].present? ? items.tagged_with(params[:topic]) : items
+ return items unless params[:topic].present?
+
+ topics = params[:topic].instance_of?(String) ? params[:topic].strip.split(/\s*,\s*/) : params[:topic]
+ topics.each do |topic|
+ items = items.with_topic(topic)
+ end
+
+ items
end
def by_search(items)
params[:search] ||= params[:name]
+ return items if Feature.enabled?(:disable_anonymous_project_search, type: :ops) && current_user.nil?
return items.none if params[:search].present? && params[:minimum_search_length].present? && params[:search].length < params[:minimum_search_length].to_i
items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?)
diff --git a/app/finders/repositories/tree_finder.rb b/app/finders/repositories/tree_finder.rb
new file mode 100644
index 00000000000..2ea5a8856ec
--- /dev/null
+++ b/app/finders/repositories/tree_finder.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module Repositories
+ class TreeFinder < GitRefsFinder
+ attr_reader :user_project
+
+ CommitMissingError = Class.new(StandardError)
+
+ def initialize(user_project, params = {})
+ super(user_project.repository, params)
+
+ @user_project = user_project
+ end
+
+ def execute(gitaly_pagination: false)
+ raise CommitMissingError unless commit_exists?
+
+ request_params = { recursive: recursive }
+ request_params[:pagination_params] = pagination_params if gitaly_pagination
+ tree = user_project.repository.tree(commit.id, path, **request_params)
+
+ tree.sorted_entries
+ end
+
+ def total
+ # This is inefficient and we'll look at replacing this implementation
+ Gitlab::Cache.fetch_once([user_project, repository.commit, :tree_size, commit.id, path, recursive]) do
+ user_project.repository.tree(commit.id, path, recursive: recursive).entries.size
+ end
+ end
+
+ def commit_exists?
+ commit.present?
+ end
+
+ private
+
+ def commit
+ @commit ||= user_project.commit(ref)
+ end
+
+ def ref
+ params[:ref] || user_project.default_branch
+ end
+
+ def path
+ params[:path]
+ end
+
+ def recursive
+ params[:recursive]
+ end
+
+ def pagination_params
+ {
+ limit: params[:per_page] || Kaminari.config.default_per_page,
+ page_token: params[:page_token]
+ }
+ end
+ end
+end