Class: Namespace

Inherits:
ApplicationRecord show all
Includes:
AfterCommitQueue, CacheMarkdownField, FeatureGate, FromUnion, Gitlab::SQL::Pattern, Gitlab::Utils::StrongMemoize, Gitlab::VisibilityLevel, IgnorableColumns, Routable, Sortable, Storage::LegacyNamespace
Defined in:
app/models/namespace/traversal_hierarchy.rb,
app/models/namespace.rb

Overview

A Namespace::TraversalHierarchy is the collection of namespaces that descend from a root Namespace as defined by the Namespace#traversal_ids attributes.

This class provides operations to be performed on the hierarchy itself, rather than individual namespaces.

This includes methods for synchronizing traversal_ids attributes to a correct state. We use recursive methods to determine the correct state so we don't have to depend on the integrity of the traversal_ids attribute values themselves.

Direct Known Subclasses

Group

Defined Under Namespace

Classes: AggregationSchedule, RootStorageStatistics, RootStorageStatisticsPolicy, TraversalHierarchy

Constant Summary collapse

NUMBER_OF_ANCESTORS_ALLOWED =

Prevent users from creating unreasonably deep level of nesting. The number 20 was taken based on maximum nesting level of Android repo (15) + some extra backup.

20

Constants included from Gitlab::SQL::Pattern

Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_WORD

Constants included from Gitlab::VisibilityLevel

Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::PUBLIC

Constants included from CacheMarkdownField

CacheMarkdownField::INVALIDATED_BY

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods included from FeatureGate

#flipper_id

Methods included from Storage::LegacyNamespace

#move_dir, #prepare_for_destroy

Methods included from Gitlab::ShellAdapter

#gitlab_shell

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from Routable

#build_full_path, #full_name, #full_path, #full_path_components, #owned_by?

Methods included from Gitlab::VisibilityLevel

allowed_for?, allowed_level?, allowed_levels, closest_allowed_level, #internal?, level_name, level_value, levels_for_user, non_restricted_level?, options, #private?, #public?, public_visibility_restricted?, restricted_level?, string_level, string_options, string_values, valid_level?, #visibility, #visibility=, #visibility_attribute_present?, #visibility_level_attributes, #visibility_level_decreased?, #visibility_level_previous_changes, #visibility_level_value

Methods included from CacheMarkdownField

#attribute_invalidated?, #banzai_render_context, #cached_html_for, #cached_html_up_to_date?, #can_cache_field?, #invalidated_markdown_cache?, #latest_cached_markdown_version, #local_version, #parent_user, #refresh_markdown_cache, #refresh_markdown_cache!, #rendered_field_content, #skip_project_check?, #updated_cached_html_for

Methods inherited from ApplicationRecord

at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, without_order

Class Method Details

.by_path(path) ⇒ Object


99
100
101
# File 'app/models/namespace.rb', line 99

def by_path(path)
  find_by('lower(path) = :value', value: path.downcase)
end

.clean_name(value) ⇒ Object


138
139
140
# File 'app/models/namespace.rb', line 138

def clean_name(value)
  value.scan(Gitlab::Regex.group_name_regex_chars).join(' ')
end

.clean_path(path) ⇒ Object


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'app/models/namespace.rb', line 119

def clean_path(path)
  path = path.dup
  # Get the email username by removing everything after an `@` sign.
  path.gsub!(/@.*\z/,                "")
  # Remove everything that's not in the list of allowed characters.
  path.gsub!(/[^a-zA-Z0-9_\-\.]/,    "")
  # Remove trailing violations ('.atom', '.git', or '.')
  path.gsub!(/(\.atom|\.git|\.)*\z/, "")
  # Remove leading violations ('-')
  path.gsub!(/\A\-+/,                "")

  # Users with the great usernames of "." or ".." would end up with a blank username.
  # Work around that by setting their username to "blank", followed by a counter.
  path = "blank" if path.blank?

  uniquify = Uniquify.new
  uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) }
end

.find_by_pages_host(host) ⇒ Object


142
143
144
145
146
147
148
149
# File 'app/models/namespace.rb', line 142

def find_by_pages_host(host)
  gitlab_host = "." + Settings.pages.host.downcase
  host = host.downcase
  return unless host.ends_with?(gitlab_host)

  name = host.delete_suffix(gitlab_host)
  Namespace.where(parent_id: nil).by_path(name)
end

.find_by_path_or_name(path) ⇒ Object

Case insensitive search for namespace by path or name


104
105
106
# File 'app/models/namespace.rb', line 104

def find_by_path_or_name(path)
  find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase)
end

.search(query) ⇒ Object

Searches for namespaces matching the given query.

This method uses ILIKE on PostgreSQL.

query - The search query as a String.

Returns an ActiveRecord::Relation.


115
116
117
# File 'app/models/namespace.rb', line 115

def search(query)
  fuzzy_search(query, [:name, :path])
end

Instance Method Details

#actual_limitsObject


370
371
372
373
374
375
# File 'app/models/namespace.rb', line 370

def actual_limits
  # We default to PlanLimits.new otherwise a lot of specs would fail
  # On production each plan should already have associated limits record
  # https://gitlab.com/gitlab-org/gitlab/issues/36037
  actual_plan.actual_limits
end

#actual_planObject


366
367
368
# File 'app/models/namespace.rb', line 366

def actual_plan
  Plan.default
end

#actual_plan_nameObject


377
378
379
# File 'app/models/namespace.rb', line 377

def actual_plan_name
  actual_plan.name
end

#aggregation_scheduled?Boolean

Returns:

  • (Boolean)

345
346
347
# File 'app/models/namespace.rb', line 345

def aggregation_scheduled?
  aggregation_schedule.present?
end

#all_pipelinesObject

Includes pipelines from this namespace and pipelines from all subgroups that belongs to this namespace


286
287
288
# File 'app/models/namespace.rb', line 286

def all_pipelines
  Ci::Pipeline.where(project: all_projects)
end

#all_projectsObject

Includes projects from this namespace and projects from all subgroups that belongs to this namespace


280
281
282
# File 'app/models/namespace.rb', line 280

def all_projects
  Project.inside_path(full_path)
end

#ancestorsObject

Returns all the ancestors of the current namespaces.


238
239
240
241
242
243
244
# File 'app/models/namespace.rb', line 238

def ancestors
  return self.class.none unless parent_id

  Gitlab::ObjectHierarchy
    .new(self.class.where(id: parent_id))
    .base_and_ancestors
end

#ancestors_upto(top = nil, hierarchy_order: nil) ⇒ Object

returns all ancestors upto but excluding the given namespace when no namespace is given, all ancestors upto the top are returned


248
249
250
251
# File 'app/models/namespace.rb', line 248

def ancestors_upto(top = nil, hierarchy_order: nil)
  Gitlab::ObjectHierarchy.new(self.class.where(id: id))
    .ancestors(upto: top, hierarchy_order: hierarchy_order)
end

#any_project_has_container_registry_tags?Boolean

Returns:

  • (Boolean)

168
169
170
# File 'app/models/namespace.rb', line 168

def any_project_has_container_registry_tags?
  all_projects.any?(&:has_container_registry_tags?)
end

#any_project_with_pages_deployed?Boolean

Returns:

  • (Boolean)

356
357
358
# File 'app/models/namespace.rb', line 356

def any_project_with_pages_deployed?
  all_projects.with_pages_deployed.any?
end

#any_project_with_shared_runners_enabled?Boolean

Returns:

  • (Boolean)

226
227
228
# File 'app/models/namespace.rb', line 226

def any_project_with_shared_runners_enabled?
  projects.with_shared_runners.any?
end

#auto_devops_enabled?Boolean

Returns:

  • (Boolean)

329
330
331
# File 'app/models/namespace.rb', line 329

def auto_devops_enabled?
  first_auto_devops_config[:status]
end

#closest_setting(name) ⇒ Object


360
361
362
363
364
# File 'app/models/namespace.rb', line 360

def closest_setting(name)
  self_and_ancestors(hierarchy_order: :asc)
    .find { |n| !n.read_attribute(name).nil? }
    .try(name)
end

#default_branch_protectionObject


152
153
154
# File 'app/models/namespace.rb', line 152

def default_branch_protection
  super || Gitlab::CurrentSettings.default_branch_protection
end

#descendantsObject

Returns all the descendants of the current namespace.


262
263
264
265
266
# File 'app/models/namespace.rb', line 262

def descendants
  Gitlab::ObjectHierarchy
    .new(self.class.where(parent_id: id))
    .base_and_descendants
end

#emails_disabled?Boolean

any ancestor can disable emails for all descendants

Returns:

  • (Boolean)

211
212
213
214
215
216
217
218
219
# File 'app/models/namespace.rb', line 211

def emails_disabled?
  strong_memoize(:emails_disabled) do
    if parent_id
      self_and_ancestors.where(emails_disabled: true).exists?
    else
      !!emails_disabled
    end
  end
end

#feature_available?(_feature) ⇒ Boolean

Overridden in EE::Namespace

Returns:

  • (Boolean)

312
313
314
# File 'app/models/namespace.rb', line 312

def feature_available?(_feature)
  false
end

#find_fork_of(project) ⇒ Object


194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'app/models/namespace.rb', line 194

def find_fork_of(project)
  return unless project.fork_network

  if Gitlab::SafeRequestStore.active?
    forks_in_namespace = Gitlab::SafeRequestStore.fetch("namespaces:#{id}:forked_projects") do
      Hash.new do |found_forks, project|
        found_forks[project] = project.fork_network.find_forks_in(projects).first
      end
    end

    forks_in_namespace[project]
  else
    project.fork_network.find_forks_in(projects).first
  end
end

#first_auto_devops_configObject


333
334
335
336
337
338
339
340
341
342
343
# File 'app/models/namespace.rb', line 333

def first_auto_devops_config
  return { scope: :group, status: auto_devops_enabled } unless auto_devops_enabled.nil?

  strong_memoize(:first_auto_devops_config) do
    if has_parent?
      parent.first_auto_devops_config
    else
      { scope: :instance, status: Gitlab::CurrentSettings.auto_devops_enabled? }
    end
  end
end

#first_project_with_container_registry_tagsObject


172
173
174
# File 'app/models/namespace.rb', line 172

def first_project_with_container_registry_tags
  all_projects.find(&:has_container_registry_tags?)
end

#full_path_before_last_saveObject


316
317
318
319
320
321
322
323
# File 'app/models/namespace.rb', line 316

def full_path_before_last_save
  if parent_id_before_last_save.nil?
    path_before_last_save
  else
    previous_parent = Group.find_by(id: parent_id_before_last_save)
    previous_parent.full_path + '/' + path_before_last_save
  end
end

#group?Boolean

Returns:

  • (Boolean)

190
191
192
# File 'app/models/namespace.rb', line 190

def group?
  type == 'Group'
end

#has_parent?Boolean

Returns:

  • (Boolean)

290
291
292
# File 'app/models/namespace.rb', line 290

def has_parent?
  parent_id.present? || parent.present?
end

#human_nameObject


164
165
166
# File 'app/models/namespace.rb', line 164

def human_name
  owner_name
end

#kindObject


182
183
184
# File 'app/models/namespace.rb', line 182

def kind
  type == 'Group' ? 'group' : 'user'
end

#lfs_enabled?Boolean

Returns:

  • (Boolean)

221
222
223
224
# File 'app/models/namespace.rb', line 221

def lfs_enabled?
  # User namespace will always default to the global setting
  Gitlab.config.lfs.enabled
end

#multiple_issue_boards_available?Boolean

Overridden on EE module

Returns:

  • (Boolean)

307
308
309
# File 'app/models/namespace.rb', line 307

def multiple_issue_boards_available?
  false
end

#pages_virtual_domainObject


349
350
351
352
353
354
# File 'app/models/namespace.rb', line 349

def pages_virtual_domain
  Pages::VirtualDomain.new(
    all_projects_with_pages.includes(:route, :project_feature),
    trim_prefix: full_path
  )
end

#refresh_project_authorizationsObject


325
326
327
# File 'app/models/namespace.rb', line 325

def refresh_project_authorizations
  owner.refresh_authorized_projects
end

#root_ancestorObject


294
295
296
297
298
299
300
# File 'app/models/namespace.rb', line 294

def root_ancestor
  return self if persisted? && parent_id.nil?

  strong_memoize(:root_ancestor) do
    self_and_ancestors.reorder(nil).find_by(parent_id: nil)
  end
end

#self_and_ancestors(hierarchy_order: nil) ⇒ Object


253
254
255
256
257
258
259
# File 'app/models/namespace.rb', line 253

def self_and_ancestors(hierarchy_order: nil)
  return self.class.where(id: id) unless parent_id

  Gitlab::ObjectHierarchy
    .new(self.class.where(id: id))
    .base_and_ancestors(hierarchy_order: hierarchy_order)
end

#self_and_descendantsObject


268
269
270
271
272
# File 'app/models/namespace.rb', line 268

def self_and_descendants
  Gitlab::ObjectHierarchy
    .new(self.class.where(id: id))
    .base_and_descendants
end

#self_and_hierarchyObject

Returns all ancestors, self, and descendants of the current namespace.


231
232
233
234
235
# File 'app/models/namespace.rb', line 231

def self_and_hierarchy
  Gitlab::ObjectHierarchy
    .new(self.class.where(id: id))
    .all_objects
end

#send_update_instructionsObject


176
177
178
179
180
# File 'app/models/namespace.rb', line 176

def send_update_instructions
  projects.each do |project|
    project.send_move_instructions("#{full_path_before_last_save}/#{project.path}")
  end
end

#subgroup?Boolean

Returns:

  • (Boolean)

302
303
304
# File 'app/models/namespace.rb', line 302

def subgroup?
  has_parent?
end

#to_paramObject


160
161
162
# File 'app/models/namespace.rb', line 160

def to_param
  full_path
end

#user?Boolean

Returns:

  • (Boolean)

186
187
188
# File 'app/models/namespace.rb', line 186

def user?
  kind == 'user'
end

#user_ids_for_project_authorizationsObject


274
275
276
# File 'app/models/namespace.rb', line 274

def user_ids_for_project_authorizations
  [owner_id]
end

#visibility_level_fieldObject


156
157
158
# File 'app/models/namespace.rb', line 156

def visibility_level_field
  :visibility_level
end