summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/analytics/cycle_analytics/issue_stage_event.rb5
-rw-r--r--app/models/analytics/cycle_analytics/merge_request_stage_event.rb5
-rw-r--r--app/models/application_setting.rb10
-rw-r--r--app/models/application_setting_implementation.rb7
-rw-r--r--app/models/audit_event.rb2
-rw-r--r--app/models/award_emoji.rb2
-rw-r--r--app/models/bulk_import.rb11
-rw-r--r--app/models/bulk_imports/entity.rb14
-rw-r--r--app/models/bulk_imports/export.rb2
-rw-r--r--app/models/bulk_imports/export_status.rb2
-rw-r--r--app/models/bulk_imports/file_transfer/base_config.rb32
-rw-r--r--app/models/bulk_imports/file_transfer/group_config.rb6
-rw-r--r--app/models/bulk_imports/file_transfer/project_config.rb15
-rw-r--r--app/models/bulk_imports/tracker.rb2
-rw-r--r--app/models/ci/bridge.rb2
-rw-r--r--app/models/ci/build.rb72
-rw-r--r--app/models/ci/build_metadata.rb4
-rw-r--r--app/models/ci/build_need.rb2
-rw-r--r--app/models/ci/build_runner_session.rb2
-rw-r--r--app/models/ci/build_trace_chunk.rb2
-rw-r--r--app/models/ci/build_trace_metadata.rb11
-rw-r--r--app/models/ci/job_token/project_scope_link.rb2
-rw-r--r--app/models/ci/job_token/scope.rb11
-rw-r--r--app/models/ci/pipeline.rb31
-rw-r--r--app/models/ci/processable.rb3
-rw-r--r--app/models/ci/resource_group.rb32
-rw-r--r--app/models/ci/runner.rb26
-rw-r--r--app/models/ci/runner_namespace.rb1
-rw-r--r--app/models/ci/runner_project.rb1
-rw-r--r--app/models/ci/stage.rb2
-rw-r--r--app/models/clusters/agents/group_authorization.rb4
-rw-r--r--app/models/clusters/agents/implicit_authorization.rb5
-rw-r--r--app/models/clusters/agents/project_authorization.rb4
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/integrations/elastic_stack.rb2
-rw-r--r--app/models/clusters/integrations/prometheus.rb2
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/commit_status.rb3
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage.rb3
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage_event_model.rb54
-rw-r--r--app/models/concerns/avatarable.rb3
-rw-r--r--app/models/concerns/bulk_insert_safe.rb12
-rw-r--r--app/models/concerns/checksummable.rb6
-rw-r--r--app/models/concerns/ci/has_status.rb1
-rw-r--r--app/models/concerns/ci/metadatable.rb1
-rw-r--r--app/models/concerns/enums/ci/commit_status.rb1
-rw-r--r--app/models/concerns/has_repository.rb4
-rw-r--r--app/models/concerns/integrations/has_data_fields.rb1
-rw-r--r--app/models/concerns/issue_available_features.rb3
-rw-r--r--app/models/concerns/packages/debian/distribution.rb10
-rw-r--r--app/models/concerns/restricted_signup.rb42
-rw-r--r--app/models/concerns/routable.rb3
-rw-r--r--app/models/concerns/ttl_expirable.rb20
-rw-r--r--app/models/concerns/vulnerability_finding_helpers.rb9
-rw-r--r--app/models/container_expiration_policy.rb1
-rw-r--r--app/models/custom_emoji.rb2
-rw-r--r--app/models/customer_relations/contact.rb3
-rw-r--r--app/models/customer_relations/organization.rb2
-rw-r--r--app/models/dependency_proxy/blob.rb5
-rw-r--r--app/models/dependency_proxy/image_ttl_group_policy.rb2
-rw-r--r--app/models/dependency_proxy/manifest.rb5
-rw-r--r--app/models/deployment.rb62
-rw-r--r--app/models/environment.rb15
-rw-r--r--app/models/environment_status.rb10
-rw-r--r--app/models/error_tracking/error.rb15
-rw-r--r--app/models/error_tracking/error_event.rb4
-rw-r--r--app/models/error_tracking/project_error_tracking_setting.rb25
-rw-r--r--app/models/group.rb20
-rw-r--r--app/models/instance_configuration.rb12
-rw-r--r--app/models/integrations/open_project.rb20
-rw-r--r--app/models/integrations/open_project_tracker_data.rb18
-rw-r--r--app/models/integrations/unify_circuit.rb11
-rw-r--r--app/models/issue.rb44
-rw-r--r--app/models/lfs_object.rb2
-rw-r--r--app/models/loose_foreign_keys/deleted_record.rb44
-rw-r--r--app/models/member.rb25
-rw-r--r--app/models/members/group_member.rb8
-rw-r--r--app/models/members/project_member.rb14
-rw-r--r--app/models/merge_request.rb32
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/metrics/dashboard/annotation.rb6
-rw-r--r--app/models/namespace.rb94
-rw-r--r--app/models/namespace/root_storage_statistics.rb2
-rw-r--r--app/models/namespaces/user_namespace.rb2
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/operations/feature_flag.rb37
-rw-r--r--app/models/operations/feature_flag_scope.rb66
-rw-r--r--app/models/packages/composer/cache_file.rb4
-rw-r--r--app/models/packages/helm/file_metadatum.rb2
-rw-r--r--app/models/pages_domain.rb13
-rw-r--r--app/models/preloaders/merge_requests_preloader.rb19
-rw-r--r--app/models/product_analytics_event.rb2
-rw-r--r--app/models/project.rb83
-rw-r--r--app/models/project_setting.rb4
-rw-r--r--app/models/project_statistics.rb3
-rw-r--r--app/models/projects/project_topic.rb2
-rw-r--r--app/models/projects/topic.rb20
-rw-r--r--app/models/protected_branch.rb2
-rw-r--r--app/models/release.rb1
-rw-r--r--app/models/repository.rb10
-rw-r--r--app/models/upload.rb12
-rw-r--r--app/models/user.rb35
-rw-r--r--app/models/user_callout.rb3
-rw-r--r--app/models/user_detail.rb7
-rw-r--r--app/models/user_highest_role.rb8
-rw-r--r--app/models/user_preference.rb1
-rw-r--r--app/models/users/credit_card_validation.rb13
107 files changed, 798 insertions, 576 deletions
diff --git a/app/models/analytics/cycle_analytics/issue_stage_event.rb b/app/models/analytics/cycle_analytics/issue_stage_event.rb
index 1da8973ff21..3e6ed86d534 100644
--- a/app/models/analytics/cycle_analytics/issue_stage_event.rb
+++ b/app/models/analytics/cycle_analytics/issue_stage_event.rb
@@ -3,9 +3,14 @@
module Analytics
module CycleAnalytics
class IssueStageEvent < ApplicationRecord
+ include StageEventModel
extend SuppressCompositePrimaryKeyWarning
validates(*%i[stage_event_hash_id issue_id group_id project_id start_event_timestamp], presence: true)
+
+ def self.issuable_id_column
+ :issue_id
+ end
end
end
end
diff --git a/app/models/analytics/cycle_analytics/merge_request_stage_event.rb b/app/models/analytics/cycle_analytics/merge_request_stage_event.rb
index d2f899ae933..d0ec3c4e8b9 100644
--- a/app/models/analytics/cycle_analytics/merge_request_stage_event.rb
+++ b/app/models/analytics/cycle_analytics/merge_request_stage_event.rb
@@ -3,9 +3,14 @@
module Analytics
module CycleAnalytics
class MergeRequestStageEvent < ApplicationRecord
+ include StageEventModel
extend SuppressCompositePrimaryKeyWarning
validates(*%i[stage_event_hash_id merge_request_id group_id project_id start_event_timestamp], presence: true)
+
+ def self.issuable_id_column
+ :merge_request_id
+ end
end
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 5f16b990d01..5a8cbd8d71c 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -9,8 +9,6 @@ class ApplicationSetting < ApplicationRecord
include Sanitizable
ignore_columns %i[elasticsearch_shards elasticsearch_replicas], remove_with: '14.4', remove_after: '2021-09-22'
- ignore_column :seat_link_enabled, remove_with: '14.4', remove_after: '2021-09-22'
- ignore_column :cloud_license_enabled, remove_with: '14.4', remove_after: '2021-09-22'
INSTANCE_REVIEW_MIN_USERS = 50
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
@@ -366,6 +364,10 @@ class ApplicationSetting < ApplicationRecord
validates :container_registry_expiration_policies_worker_capacity,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+ validates :dependency_proxy_ttl_group_policy_worker_capacity,
+ allow_nil: false,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+
validates :invisible_captcha_enabled,
inclusion: { in: [true, false], message: _('must be a boolean value') }
@@ -481,6 +483,8 @@ class ApplicationSetting < ApplicationRecord
validates :throttle_unauthenticated_packages_api_period_in_seconds
validates :throttle_unauthenticated_files_api_requests_per_period
validates :throttle_unauthenticated_files_api_period_in_seconds
+ validates :throttle_unauthenticated_deprecated_api_requests_per_period
+ validates :throttle_unauthenticated_deprecated_api_period_in_seconds
validates :throttle_authenticated_api_requests_per_period
validates :throttle_authenticated_api_period_in_seconds
validates :throttle_authenticated_git_lfs_requests_per_period
@@ -491,6 +495,8 @@ class ApplicationSetting < ApplicationRecord
validates :throttle_authenticated_packages_api_period_in_seconds
validates :throttle_authenticated_files_api_requests_per_period
validates :throttle_authenticated_files_api_period_in_seconds
+ validates :throttle_authenticated_deprecated_api_requests_per_period
+ validates :throttle_authenticated_deprecated_api_period_in_seconds
validates :throttle_protected_paths_requests_per_period
validates :throttle_protected_paths_period_in_seconds
end
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 612fda158d3..7bdea36bb8a 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -159,6 +159,7 @@ module ApplicationSettingImplementation
spam_check_endpoint_enabled: false,
spam_check_endpoint_url: nil,
spam_check_api_key: nil,
+ suggest_pipeline_enabled: true,
terminal_max_session_time: 0,
throttle_authenticated_api_enabled: false,
throttle_authenticated_api_period_in_seconds: 3600,
@@ -175,6 +176,9 @@ module ApplicationSettingImplementation
throttle_authenticated_files_api_enabled: false,
throttle_authenticated_files_api_period_in_seconds: 15,
throttle_authenticated_files_api_requests_per_period: 500,
+ throttle_authenticated_deprecated_api_enabled: false,
+ throttle_authenticated_deprecated_api_period_in_seconds: 3600,
+ throttle_authenticated_deprecated_api_requests_per_period: 3600,
throttle_incident_management_notification_enabled: false,
throttle_incident_management_notification_per_period: 3600,
throttle_incident_management_notification_period_in_seconds: 3600,
@@ -193,6 +197,9 @@ module ApplicationSettingImplementation
throttle_unauthenticated_files_api_enabled: false,
throttle_unauthenticated_files_api_period_in_seconds: 15,
throttle_unauthenticated_files_api_requests_per_period: 125,
+ throttle_unauthenticated_deprecated_api_enabled: false,
+ throttle_unauthenticated_deprecated_api_period_in_seconds: 3600,
+ throttle_unauthenticated_deprecated_api_requests_per_period: 1800,
time_tracking_limit_to_hours: false,
two_factor_grace_period: 48,
unique_ips_limit_enabled: false,
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index f17fff742fe..a1c6793607f 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -71,7 +71,7 @@ class AuditEvent < ApplicationRecord
end
def lazy_author
- BatchLoader.for(author_id).batch(replace_methods: false) do |author_ids, loader|
+ BatchLoader.for(author_id).batch do |author_ids, loader|
User.select(:id, :name, :username).where(id: author_ids).find_each do |user|
loader.call(user.id, user)
end
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index d251b0adbd3..c8f6b9aaedb 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -66,5 +66,3 @@ class AwardEmoji < ApplicationRecord
awardable.try(:update_upvotes_count) if upvote?
end
end
-
-AwardEmoji.prepend_mod_with('AwardEmoji')
diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb
index dee55675304..818ae04ba29 100644
--- a/app/models/bulk_import.rb
+++ b/app/models/bulk_import.rb
@@ -4,7 +4,8 @@
# projects to a GitLab instance. It associates the import with the responsible
# user.
class BulkImport < ApplicationRecord
- MINIMUM_GITLAB_MAJOR_VERSION = 14
+ MIN_MAJOR_VERSION = 14
+ MIN_MINOR_VERSION_FOR_PROJECT = 4
belongs_to :user, optional: false
@@ -34,6 +35,14 @@ class BulkImport < ApplicationRecord
end
end
+ def source_version_info
+ Gitlab::VersionInfo.parse(source_version)
+ end
+
+ def self.min_gl_version_for_project_migration
+ Gitlab::VersionInfo.new(MIN_MAJOR_VERSION, MIN_MINOR_VERSION_FOR_PROJECT)
+ end
+
def self.all_human_statuses
state_machine.states.map(&:human_name)
end
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index ab5d248ff8c..ecac4ab95f4 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -20,6 +20,8 @@
class BulkImports::Entity < ApplicationRecord
self.table_name = 'bulk_import_entities'
+ EXPORT_RELATIONS_URL = '/%{resource}/%{full_path}/export_relations'
+
belongs_to :bulk_import, optional: false
belongs_to :parent, class_name: 'BulkImports::Entity', optional: true
@@ -81,9 +83,9 @@ class BulkImports::Entity < ApplicationRecord
def pipelines
@pipelines ||= case source_type
when 'group_entity'
- BulkImports::Groups::Stage.pipelines
+ BulkImports::Groups::Stage.new(bulk_import).pipelines
when 'project_entity'
- BulkImports::Projects::Stage.pipelines
+ BulkImports::Projects::Stage.new(bulk_import).pipelines
end
end
@@ -102,6 +104,14 @@ class BulkImports::Entity < ApplicationRecord
end
end
+ def pluralized_name
+ source_type.gsub('_entity', '').pluralize
+ end
+
+ def export_relations_url_path
+ @export_relations_url_path ||= EXPORT_RELATIONS_URL % { resource: pluralized_name, full_path: encoded_source_full_path }
+ end
+
private
def validate_parent_is_a_group
diff --git a/app/models/bulk_imports/export.rb b/app/models/bulk_imports/export.rb
index 371b58dea03..8d4d31ee92d 100644
--- a/app/models/bulk_imports/export.rb
+++ b/app/models/bulk_imports/export.rb
@@ -53,7 +53,7 @@ module BulkImports
end
def relation_definition
- config.portable_tree[:include].find { |include| include[relation.to_sym] }
+ config.relation_definition_for(relation)
end
def config
diff --git a/app/models/bulk_imports/export_status.rb b/app/models/bulk_imports/export_status.rb
index ff165830cf1..abf064adaae 100644
--- a/app/models/bulk_imports/export_status.rb
+++ b/app/models/bulk_imports/export_status.rb
@@ -41,7 +41,7 @@ module BulkImports
end
def status_endpoint
- "/groups/#{entity.encoded_source_full_path}/export_relations/status"
+ File.join(entity.export_relations_url_path, 'status')
end
end
end
diff --git a/app/models/bulk_imports/file_transfer/base_config.rb b/app/models/bulk_imports/file_transfer/base_config.rb
index ddea7c3f64c..4d370315ad5 100644
--- a/app/models/bulk_imports/file_transfer/base_config.rb
+++ b/app/models/bulk_imports/file_transfer/base_config.rb
@@ -22,15 +22,25 @@ module BulkImports
end
def export_path
- strong_memoize(:export_path) do
- relative_path = File.join(base_export_path, SecureRandom.hex)
-
- ::Gitlab::ImportExport.export_path(relative_path: relative_path)
- end
+ @export_path ||= Dir.mktmpdir('bulk_imports')
end
def portable_relations
- import_export_config.dig(:tree, portable_class_sym).keys.map(&:to_s) - skipped_relations
+ tree_relations + file_relations - skipped_relations
+ end
+
+ def tree_relation?(relation)
+ tree_relations.include?(relation)
+ end
+
+ def file_relation?(relation)
+ file_relations.include?(relation)
+ end
+
+ def tree_relation_definition_for(relation)
+ return unless tree_relation?(relation)
+
+ portable_tree[:include].find { |include| include[relation.to_sym] }
end
private
@@ -44,7 +54,7 @@ module BulkImports
end
def import_export_config
- ::Gitlab::ImportExport::Config.new(config: import_export_yaml).to_h
+ @config ||= ::Gitlab::ImportExport::Config.new(config: import_export_yaml).to_h
end
def portable_class
@@ -63,8 +73,12 @@ module BulkImports
raise NotImplementedError
end
- def base_export_path
- raise NotImplementedError
+ def tree_relations
+ import_export_config.dig(:tree, portable_class_sym).keys.map(&:to_s)
+ end
+
+ def file_relations
+ []
end
def skipped_relations
diff --git a/app/models/bulk_imports/file_transfer/group_config.rb b/app/models/bulk_imports/file_transfer/group_config.rb
index 2266cbb484f..6766c00246b 100644
--- a/app/models/bulk_imports/file_transfer/group_config.rb
+++ b/app/models/bulk_imports/file_transfer/group_config.rb
@@ -3,16 +3,14 @@
module BulkImports
module FileTransfer
class GroupConfig < BaseConfig
- def base_export_path
- portable.full_path
- end
+ SKIPPED_RELATIONS = %w(members).freeze
def import_export_yaml
::Gitlab::ImportExport.group_config_file
end
def skipped_relations
- @skipped_relations ||= %w(members)
+ SKIPPED_RELATIONS
end
end
end
diff --git a/app/models/bulk_imports/file_transfer/project_config.rb b/app/models/bulk_imports/file_transfer/project_config.rb
index 8a57f51c1c5..9a0434da08a 100644
--- a/app/models/bulk_imports/file_transfer/project_config.rb
+++ b/app/models/bulk_imports/file_transfer/project_config.rb
@@ -3,16 +3,23 @@
module BulkImports
module FileTransfer
class ProjectConfig < BaseConfig
- def base_export_path
- portable.disk_path
- end
+ UPLOADS_RELATION = 'uploads'
+
+ SKIPPED_RELATIONS = %w(
+ project_members
+ group_members
+ ).freeze
def import_export_yaml
::Gitlab::ImportExport.config_file
end
+ def file_relations
+ [UPLOADS_RELATION]
+ end
+
def skipped_relations
- @skipped_relations ||= %w(project_members group_members)
+ SKIPPED_RELATIONS
end
end
end
diff --git a/app/models/bulk_imports/tracker.rb b/app/models/bulk_imports/tracker.rb
index c185470b1c2..9de3239ee0f 100644
--- a/app/models/bulk_imports/tracker.rb
+++ b/app/models/bulk_imports/tracker.rb
@@ -50,6 +50,8 @@ class BulkImports::Tracker < ApplicationRecord
event :start do
transition created: :started
+ # To avoid errors when re-starting a pipeline in case of network errors
+ transition started: :started
end
event :finish do
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index 97fb8233d34..50bda64d537 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -31,7 +31,7 @@ module Ci
next unless bridge.triggers_downstream_pipeline?
bridge.run_after_commit do
- ::Ci::CreateCrossProjectPipelineWorker.perform_async(bridge.id)
+ ::Ci::CreateDownstreamPipelineWorker.perform_async(bridge.id)
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e2e24247679..990ef71a457 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -42,6 +42,10 @@ module Ci
has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id, inverse_of: :build
has_many :report_results, class_name: 'Ci::BuildReportResult', inverse_of: :build
+ # Projects::DestroyService destroys Ci::Pipelines, which use_fast_destroy on :job_artifacts
+ # before we delete builds. By doing this, the relation should be empty and not fire any
+ # DELETE queries when the Ci::Build is destroyed. The next step is to remove `dependent: :destroy`.
+ # Details: https://gitlab.com/gitlab-org/gitlab/-/issues/24644#note_689472685
has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent
has_many :job_variables, class_name: 'Ci::JobVariable', foreign_key: :job_id
has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_job_id
@@ -55,6 +59,8 @@ module Ci
has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build
has_one :trace_metadata, class_name: 'Ci::BuildTraceMetadata', inverse_of: :build
+ has_many :terraform_state_versions, class_name: 'Terraform::StateVersion', dependent: :nullify, inverse_of: :build, foreign_key: :ci_build_id # rubocop:disable Cop/ActiveRecordDependent
+
accepts_nested_attributes_for :runner_session, update_only: true
accepts_nested_attributes_for :job_variables
@@ -64,8 +70,8 @@ module Ci
delegate :gitlab_deploy_token, to: :project
delegate :trigger_short_token, to: :trigger_request, allow_nil: true
- ignore_columns :id_convert_to_bigint, remove_with: '14.1', remove_after: '2021-07-22'
- ignore_columns :stage_id_convert_to_bigint, remove_with: '14.1', remove_after: '2021-07-22'
+ ignore_columns :id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
+ ignore_columns :stage_id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
##
# Since Gitlab 11.5, deployments records started being created right after
@@ -192,7 +198,6 @@ module Ci
add_authentication_token_field :token, encrypted: :required
before_save :ensure_token
- before_destroy { unscoped_project }
after_save :stick_build_if_status_changed
@@ -308,8 +313,10 @@ module Ci
end
after_transition pending: :running do |build|
- Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
- build.deployment&.run
+ unless build.update_deployment_after_transaction_commit?
+ Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
+ build.deployment&.run
+ end
end
build.run_after_commit do
@@ -332,8 +339,10 @@ module Ci
end
after_transition any => [:success] do |build|
- Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
- build.deployment&.succeed
+ unless build.update_deployment_after_transaction_commit?
+ Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
+ build.deployment&.succeed
+ end
end
build.run_after_commit do
@@ -346,12 +355,14 @@ module Ci
next unless build.project
next unless build.deployment
- begin
- Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
- build.deployment.drop!
+ unless build.update_deployment_after_transaction_commit?
+ begin
+ Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
+ build.deployment.drop!
+ end
+ rescue StandardError => e
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, build_id: build.id)
end
- rescue StandardError => e
- Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, build_id: build.id)
end
true
@@ -370,14 +381,29 @@ module Ci
end
after_transition any => [:skipped, :canceled] do |build, transition|
- Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
- if transition.to_name == :skipped
- build.deployment&.skip
- else
- build.deployment&.cancel
+ unless build.update_deployment_after_transaction_commit?
+ Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
+ if transition.to_name == :skipped
+ build.deployment&.skip
+ else
+ build.deployment&.cancel
+ end
end
end
end
+
+ # Synchronize Deployment Status
+ # Please note that the data integirty is not assured because we can't use
+ # a database transaction due to DB decomposition.
+ after_transition do |build, transition|
+ next if transition.loopback?
+ next unless build.project
+ next unless build.update_deployment_after_transaction_commit?
+
+ build.run_after_commit do
+ build.deployment&.sync_status_with(build)
+ end
+ end
end
def self.build_matchers(project)
@@ -1094,6 +1120,12 @@ module Ci
runner&.instance_type?
end
+ def update_deployment_after_transaction_commit?
+ strong_memoize(:update_deployment_after_transaction_commit) do
+ Feature.enabled?(:update_deployment_after_transaction_commit, project, default_enabled: :yaml)
+ end
+ end
+
protected
def run_status_commit_hooks!
@@ -1108,7 +1140,7 @@ module Ci
return unless saved_change_to_status?
return unless running?
- ::Gitlab::Database::LoadBalancing::Sticking.stick(:build, id)
+ self.class.sticking.stick(:build, id)
end
def status_commit_hooks
@@ -1154,10 +1186,6 @@ module Ci
self.update(erased_by: user, erased_at: Time.current, artifacts_expire_at: nil)
end
- def unscoped_project
- @unscoped_project ||= Project.unscoped.find_by(id: project_id)
- end
-
def environment_url
options&.dig(:environment, :url) || persisted_environment&.external_url
end
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index 90237a4be52..0d6d6f7a6a5 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -37,8 +37,8 @@ module Ci
job_timeout_source: 4
}
- ignore_column :build_id_convert_to_bigint, remove_with: '14.2', remove_after: '2021-08-22'
- ignore_columns :id_convert_to_bigint, remove_with: '14.3', remove_after: '2021-09-22'
+ ignore_column :build_id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
+ ignore_columns :id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
def update_timeout_state
timeout = timeout_with_highest_precedence
diff --git a/app/models/ci/build_need.rb b/app/models/ci/build_need.rb
index 003659570b3..bf1470ca20f 100644
--- a/app/models/ci/build_need.rb
+++ b/app/models/ci/build_need.rb
@@ -5,8 +5,6 @@ module Ci
include BulkInsertSafe
include IgnorableColumns
- ignore_columns :build_id_convert_to_bigint, remove_with: '14.1', remove_after: '2021-07-22'
-
belongs_to :build, class_name: "Ci::Processable", foreign_key: :build_id, inverse_of: :needs
validates :build, presence: true
diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb
index 45de47116cd..e12c0f82c99 100644
--- a/app/models/ci/build_runner_session.rb
+++ b/app/models/ci/build_runner_session.rb
@@ -6,8 +6,6 @@ module Ci
class BuildRunnerSession < Ci::ApplicationRecord
include IgnorableColumns
- ignore_columns :build_id_convert_to_bigint, remove_with: '14.1', remove_after: '2021-07-22'
-
TERMINAL_SUBPROTOCOL = 'terminal.gitlab.com'
DEFAULT_SERVICE_NAME = 'build'
DEFAULT_PORT_NAME = 'default_port'
diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb
index 7a15d7ba940..6edb5ef4579 100644
--- a/app/models/ci/build_trace_chunk.rb
+++ b/app/models/ci/build_trace_chunk.rb
@@ -9,8 +9,6 @@ module Ci
include ::Gitlab::OptimisticLocking
include IgnorableColumns
- ignore_columns :build_id_convert_to_bigint, remove_with: '14.1', remove_after: '2021-07-22'
-
belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id
default_value_for :data_store, :redis_trace_chunks
diff --git a/app/models/ci/build_trace_metadata.rb b/app/models/ci/build_trace_metadata.rb
index 901b84ceec6..1ffa0e31f99 100644
--- a/app/models/ci/build_trace_metadata.rb
+++ b/app/models/ci/build_trace_metadata.rb
@@ -37,8 +37,10 @@ module Ci
increment!(:archival_attempts, touch: :last_archival_attempt_at)
end
- def track_archival!(trace_artifact_id)
- update!(trace_artifact_id: trace_artifact_id, archived_at: Time.current)
+ def track_archival!(trace_artifact_id, checksum)
+ update!(trace_artifact_id: trace_artifact_id,
+ checksum: checksum,
+ archived_at: Time.current)
end
def archival_attempts_message
@@ -49,6 +51,11 @@ module Ci
end
end
+ def remote_checksum_valid?
+ checksum.present? &&
+ checksum == remote_checksum
+ end
+
private
def backoff
diff --git a/app/models/ci/job_token/project_scope_link.rb b/app/models/ci/job_token/project_scope_link.rb
index 99118f8090b..c2ab8ca0929 100644
--- a/app/models/ci/job_token/project_scope_link.rb
+++ b/app/models/ci/job_token/project_scope_link.rb
@@ -5,7 +5,7 @@
module Ci
module JobToken
- class ProjectScopeLink < ApplicationRecord
+ class ProjectScopeLink < Ci::ApplicationRecord
self.table_name = 'ci_job_token_project_scope_links'
belongs_to :source_project, class_name: 'Project'
diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb
index 42cfdc21d66..3a5765aa00c 100644
--- a/app/models/ci/job_token/scope.rb
+++ b/app/models/ci/job_token/scope.rb
@@ -32,12 +32,15 @@ module Ci
def all_projects
Project.from_union([
Project.id_in(source_project),
- Project.where_exists(
- Ci::JobToken::ProjectScopeLink
- .from_project(source_project)
- .where('projects.id = ci_job_token_project_scope_links.target_project_id'))
+ Project.id_in(target_project_ids)
], remove_duplicates: false)
end
+
+ private
+
+ def target_project_ids
+ Ci::JobToken::ProjectScopeLink.from_project(source_project).pluck(:target_project_id)
+ end
end
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 1a0cec3c935..0041ec5135c 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -82,7 +82,8 @@ module Ci
# Merge requests for which the current pipeline is running against
# the merge request's latest commit.
has_many :merge_requests_as_head_pipeline, foreign_key: "head_pipeline_id", class_name: 'MergeRequest'
-
+ has_many :package_build_infos, class_name: 'Packages::BuildInfo', dependent: :nullify, inverse_of: :pipeline # rubocop:disable Cop/ActiveRecordDependent
+ has_many :package_file_build_infos, class_name: 'Packages::PackageFileBuildInfo', dependent: :nullify, inverse_of: :pipeline # rubocop:disable Cop/ActiveRecordDependent
has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
has_many :failed_builds, -> { latest.failed }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
has_many :retryable_builds, -> { latest.failed_or_canceled.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
@@ -861,11 +862,6 @@ module Ci
self.duration = Gitlab::Ci::Pipeline::Duration.from_pipeline(self)
end
- def execute_hooks
- project.execute_hooks(pipeline_data, :pipeline_hooks) if project.has_active_hooks?(:pipeline_hooks)
- project.execute_integrations(pipeline_data, :pipeline_hooks) if project.has_active_integrations?(:pipeline_hooks)
- end
-
# All the merge requests for which the current pipeline runs/ran against
def all_merge_requests
@all_merge_requests ||=
@@ -929,9 +925,22 @@ module Ci
end
def environments_in_self_and_descendants
- environment_ids = self_and_descendants.joins(:deployments).select(:'deployments.environment_id')
+ if ::Feature.enabled?(:avoid_cross_joins_environments_in_self_and_descendants, default_enabled: :yaml)
+ # We limit to 100 unique environments for application safety.
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/340781#note_699114700
+ expanded_environment_names =
+ builds_in_self_and_descendants.joins(:metadata)
+ .where.not('ci_builds_metadata.expanded_environment_name' => nil)
+ .distinct('ci_builds_metadata.expanded_environment_name')
+ .limit(100)
+ .pluck(:expanded_environment_name)
+
+ Environment.where(project: project, name: expanded_environment_names)
+ else
+ environment_ids = self_and_descendants.joins(:deployments).select(:'deployments.environment_id')
- Environment.where(id: environment_ids)
+ Environment.where(id: environment_ids)
+ end
end
# With multi-project and parent-child pipelines
@@ -1251,12 +1260,6 @@ module Ci
messages.build(severity: severity, content: content)
end
- def pipeline_data
- strong_memoize(:pipeline_data) do
- Gitlab::DataBuilder::Pipeline.build(self)
- end
- end
-
def merge_request_diff_sha
return unless merge_request?
diff --git a/app/models/ci/processable.rb b/app/models/ci/processable.rb
index 30d335fd7d5..372df8cc264 100644
--- a/app/models/ci/processable.rb
+++ b/app/models/ci/processable.rb
@@ -58,7 +58,8 @@ module Ci
after_transition any => ::Ci::Processable.completed_statuses do |processable|
next unless processable.with_resource_group?
- next unless processable.resource_group.release_resource_from(processable)
+
+ processable.resource_group.release_resource_from(processable)
processable.run_after_commit do
Ci::ResourceGroups::AssignResourceFromResourceGroupWorker
diff --git a/app/models/ci/resource_group.rb b/app/models/ci/resource_group.rb
index 8a7456041e6..6d25f747a9d 100644
--- a/app/models/ci/resource_group.rb
+++ b/app/models/ci/resource_group.rb
@@ -14,6 +14,12 @@ module Ci
before_create :ensure_resource
+ enum process_mode: {
+ unordered: 0,
+ oldest_first: 1,
+ newest_first: 2
+ }
+
##
# NOTE: This is concurrency-safe method that the subquery in the `UPDATE`
# works as explicit locking.
@@ -25,8 +31,34 @@ module Ci
resources.retained_by(processable).update_all(build_id: nil) > 0
end
+ def upcoming_processables
+ if unordered?
+ processables.waiting_for_resource
+ elsif oldest_first?
+ processables.waiting_for_resource_or_upcoming
+ .order(Arel.sql("commit_id ASC, #{sort_by_job_status}"))
+ elsif newest_first?
+ processables.waiting_for_resource_or_upcoming
+ .order(Arel.sql("commit_id DESC, #{sort_by_job_status}"))
+ else
+ Ci::Processable.none
+ end
+ end
+
private
+ # In order to avoid deadlock, we do NOT specify the job execution order in the same pipeline.
+ # The system processes wherever ready to transition to `pending` status from `waiting_for_resource`.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/202186 for more information.
+ def sort_by_job_status
+ <<~SQL
+ CASE status
+ WHEN 'waiting_for_resource' THEN 0
+ ELSE 1
+ END ASC
+ SQL
+ end
+
def ensure_resource
# Currently we only support one resource per group, which means
# maximum one build can be set to the resource group, thus builds
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 4aa232ad26b..2f718ad7582 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -51,7 +51,7 @@ module Ci
has_many :runner_projects, inverse_of: :runner, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :runner_projects
has_many :runner_namespaces, inverse_of: :runner, autosave: true
- has_many :groups, through: :runner_namespaces
+ has_many :groups, through: :runner_namespaces, disable_joins: true
has_one :last_build, -> { order('id DESC') }, class_name: 'Ci::Build'
@@ -246,7 +246,7 @@ module Ci
begin
transaction do
- self.projects << project
+ self.runner_projects << ::Ci::RunnerProject.new(project: project, runner: self)
self.save!
end
rescue ActiveRecord::RecordInvalid => e
@@ -280,7 +280,7 @@ module Ci
end
def belongs_to_more_than_one_project?
- self.projects.limit(2).count(:all) > 1
+ runner_projects.limit(2).count(:all) > 1
end
def assigned_to_group?
@@ -309,7 +309,9 @@ module Ci
end
def only_for?(project)
- projects == [project]
+ ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') do
+ projects == [project]
+ end
end
def short_sha
@@ -344,7 +346,7 @@ module Ci
# intention here is not to execute `Ci::RegisterJobService#execute` on
# the primary database.
#
- ::Gitlab::Database::LoadBalancing::Sticking.stick(:runner, id)
+ ::Ci::Runner.sticking.stick(:runner, id)
SecureRandom.hex.tap do |new_update|
::Gitlab::Workhorse.set_key_and_notify(runner_queue_key, new_update,
@@ -428,10 +430,8 @@ module Ci
end
def no_projects
- ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') do
- if projects.any?
- errors.add(:runner, 'cannot have projects assigned')
- end
+ if runner_projects.any?
+ errors.add(:runner, 'cannot have projects assigned')
end
end
@@ -444,14 +444,16 @@ module Ci
end
def any_project
- unless projects.any?
+ unless runner_projects.any?
errors.add(:runner, 'needs to be assigned to at least one project')
end
end
def exactly_one_group
- unless groups.one?
- errors.add(:runner, 'needs to be assigned to exactly one group')
+ ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338659') do
+ unless groups.one?
+ errors.add(:runner, 'needs to be assigned to exactly one group')
+ end
end
end
diff --git a/app/models/ci/runner_namespace.rb b/app/models/ci/runner_namespace.rb
index d1353b97ed9..52a31863fb2 100644
--- a/app/models/ci/runner_namespace.rb
+++ b/app/models/ci/runner_namespace.rb
@@ -7,7 +7,6 @@ module Ci
self.limit_name = 'ci_registered_group_runners'
self.limit_scope = :group
self.limit_relation = :recent_runners
- self.limit_feature_flag = :ci_runner_limits
self.limit_feature_flag_for_override = :ci_runner_limits_override
belongs_to :runner, inverse_of: :runner_namespaces
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index e1c435e9b1f..148a29a0f8b 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -7,7 +7,6 @@ module Ci
self.limit_name = 'ci_registered_project_runners'
self.limit_scope = :project
self.limit_relation = :recent_runners
- self.limit_feature_flag = :ci_runner_limits
self.limit_feature_flag_for_override = :ci_runner_limits_override
belongs_to :runner, inverse_of: :runner_projects
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index 39e26bf2785..131e18adf62 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -8,8 +8,6 @@ module Ci
include Presentable
include IgnorableColumns
- ignore_column :id_convert_to_bigint, remove_with: '14.2', remove_after: '2021-08-22'
-
enum status: Ci::HasStatus::STATUSES_ENUM
belongs_to :project
diff --git a/app/models/clusters/agents/group_authorization.rb b/app/models/clusters/agents/group_authorization.rb
index 74c0cec3b7e..28a711aaf17 100644
--- a/app/models/clusters/agents/group_authorization.rb
+++ b/app/models/clusters/agents/group_authorization.rb
@@ -10,7 +10,9 @@ module Clusters
validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' }
- delegate :project, to: :agent
+ def config_project
+ agent.project
+ end
end
end
end
diff --git a/app/models/clusters/agents/implicit_authorization.rb b/app/models/clusters/agents/implicit_authorization.rb
index 967cc686045..9f7f653ed65 100644
--- a/app/models/clusters/agents/implicit_authorization.rb
+++ b/app/models/clusters/agents/implicit_authorization.rb
@@ -6,12 +6,15 @@ module Clusters
attr_reader :agent
delegate :id, to: :agent, prefix: true
- delegate :project, to: :agent
def initialize(agent:)
@agent = agent
end
+ def config_project
+ agent.project
+ end
+
def config
nil
end
diff --git a/app/models/clusters/agents/project_authorization.rb b/app/models/clusters/agents/project_authorization.rb
index 1c71a0a432a..f6d19086751 100644
--- a/app/models/clusters/agents/project_authorization.rb
+++ b/app/models/clusters/agents/project_authorization.rb
@@ -9,6 +9,10 @@ module Clusters
belongs_to :project, class_name: '::Project', optional: false
validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' }
+
+ def config_project
+ agent.project
+ end
end
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 993ccb33655..7cef92ce81a 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -72,7 +72,7 @@ module Clusters
if cluster.group_type?
attributes[:groups] = [group]
elsif cluster.project_type?
- attributes[:projects] = [project]
+ attributes[:runner_projects] = [::Ci::RunnerProject.new(project: project)]
end
attributes
diff --git a/app/models/clusters/integrations/elastic_stack.rb b/app/models/clusters/integrations/elastic_stack.rb
index 565d268259a..97d73d252b9 100644
--- a/app/models/clusters/integrations/elastic_stack.rb
+++ b/app/models/clusters/integrations/elastic_stack.rb
@@ -14,6 +14,8 @@ module Clusters
validates :cluster, presence: true
validates :enabled, inclusion: { in: [true, false] }
+ scope :enabled, -> { where(enabled: true) }
+
def available?
enabled
end
diff --git a/app/models/clusters/integrations/prometheus.rb b/app/models/clusters/integrations/prometheus.rb
index 3f2c47d48e6..d745a49afc1 100644
--- a/app/models/clusters/integrations/prometheus.rb
+++ b/app/models/clusters/integrations/prometheus.rb
@@ -21,6 +21,8 @@ module Clusters
default_value_for(:alert_manager_token) { SecureRandom.hex }
+ scope :enabled, -> { where(enabled: true) }
+
after_destroy do
run_after_commit do
deactivate_project_integrations
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 6c8b4ae1139..553681ee960 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -133,7 +133,7 @@ class Commit
end
def lazy(container, oid)
- BatchLoader.for({ container: container, oid: oid }).batch(replace_methods: false) do |items, loader|
+ BatchLoader.for({ container: container, oid: oid }).batch do |items, loader|
items_by_container = items.group_by { |i| i[:container] }
items_by_container.each do |container, commit_ids|
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 8cba3d04502..43427e2ebc7 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -62,6 +62,9 @@ class CommitStatus < Ci::ApplicationRecord
scope :updated_before, ->(lookback:, timeout:) {
where('(ci_builds.created_at BETWEEN ? AND ?) AND (ci_builds.updated_at BETWEEN ? AND ?)', lookback, timeout, lookback, timeout)
}
+ scope :scheduled_at_before, ->(date) {
+ where('ci_builds.scheduled_at IS NOT NULL AND ci_builds.scheduled_at < ?', date)
+ }
# The scope applies `pluck` to split the queries. Use with care.
scope :for_project_paths, -> (paths) do
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb
index 7bb6004ca83..d9e6756ab86 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage.rb
@@ -27,7 +27,8 @@ module Analytics
alias_attribute :custom_stage?, :custom
scope :default_stages, -> { where(custom: false) }
scope :ordered, -> { order(:relative_position, :id) }
- scope :for_list, -> { includes(:start_event_label, :end_event_label).ordered }
+ scope :with_preloaded_labels, -> { includes(:start_event_label, :end_event_label) }
+ scope :for_list, -> { with_preloaded_labels.ordered }
scope :by_value_stream, -> (value_stream) { where(value_stream_id: value_stream.id) }
before_save :ensure_stage_event_hash_id
diff --git a/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb b/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
new file mode 100644
index 00000000000..7462e1e828b
--- /dev/null
+++ b/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Analytics
+ module CycleAnalytics
+ module StageEventModel
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def upsert_data(data)
+ upsert_values = data.map do |row|
+ row.values_at(
+ :stage_event_hash_id,
+ :issuable_id,
+ :group_id,
+ :project_id,
+ :author_id,
+ :milestone_id,
+ :start_event_timestamp,
+ :end_event_timestamp
+ )
+ end
+
+ value_list = Arel::Nodes::ValuesList.new(upsert_values).to_sql
+
+ query = <<~SQL
+ INSERT INTO #{quoted_table_name}
+ (
+ stage_event_hash_id,
+ #{connection.quote_column_name(issuable_id_column)},
+ group_id,
+ project_id,
+ milestone_id,
+ author_id,
+ start_event_timestamp,
+ end_event_timestamp
+ )
+ #{value_list}
+ ON CONFLICT(stage_event_hash_id, #{issuable_id_column})
+ DO UPDATE SET
+ group_id = excluded.group_id,
+ project_id = excluded.project_id,
+ start_event_timestamp = excluded.start_event_timestamp,
+ end_event_timestamp = excluded.end_event_timestamp,
+ milestone_id = excluded.milestone_id,
+ author_id = excluded.author_id
+ SQL
+
+ result = connection.execute(query)
+ result.cmd_tuples
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index 84a74386ff7..b32502c3ee2 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -18,6 +18,7 @@ module Avatarable
prepend ShadowMethods
include ObjectStorage::BackgroundMove
include Gitlab::Utils::StrongMemoize
+ include ApplicationHelper
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validates :avatar, file_size: { maximum: MAXIMUM_FILE_SIZE }, if: :avatar_changed?
@@ -110,7 +111,7 @@ module Avatarable
def retrieve_upload_from_batch(identifier)
BatchLoader.for(identifier: identifier, model: self)
- .batch(key: self.class, cache: true, replace_methods: false) do |upload_params, loader, args|
+ .batch(key: self.class) do |upload_params, loader, args|
model_class = args[:key]
paths = upload_params.flat_map do |params|
params[:model].upload_paths(params[:identifier])
diff --git a/app/models/concerns/bulk_insert_safe.rb b/app/models/concerns/bulk_insert_safe.rb
index 908f0b6a7e2..6c3093ca916 100644
--- a/app/models/concerns/bulk_insert_safe.rb
+++ b/app/models/concerns/bulk_insert_safe.rb
@@ -51,6 +51,12 @@ module BulkInsertSafe
PrimaryKeySetError = Class.new(StandardError)
class_methods do
+ def insert_all_proxy_class
+ @insert_all_proxy_class ||= Class.new(self) do
+ attr_readonly :created_at
+ end
+ end
+
def set_callback(name, *args)
unless _bulk_insert_callback_allowed?(name, args)
raise MethodNotAllowedError,
@@ -138,7 +144,7 @@ module BulkInsertSafe
when nil
false
else
- raise ArgumentError, "returns needs to be :ids or nil"
+ returns
end
# Handle insertions for tables with a composite primary key
@@ -153,9 +159,9 @@ module BulkInsertSafe
item_batch, validate, &handle_attributes)
ActiveRecord::InsertAll
- .new(self, attributes, on_duplicate: on_duplicate, returning: returning, unique_by: unique_by)
+ .new(insert_all_proxy_class, attributes, on_duplicate: on_duplicate, returning: returning, unique_by: unique_by)
.execute
- .pluck(primary_key)
+ .cast_values(insert_all_proxy_class.attribute_types).to_a
end
end
end
diff --git a/app/models/concerns/checksummable.rb b/app/models/concerns/checksummable.rb
index 056abafd0ce..9812c62fcc4 100644
--- a/app/models/concerns/checksummable.rb
+++ b/app/models/concerns/checksummable.rb
@@ -8,8 +8,12 @@ module Checksummable
Zlib.crc32(data)
end
- def hexdigest(path)
+ def sha256_hexdigest(path)
::Digest::SHA256.file(path).hexdigest
end
+
+ def md5_hexdigest(path)
+ ::Digest::MD5.file(path).hexdigest
+ end
end
end
diff --git a/app/models/concerns/ci/has_status.rb b/app/models/concerns/ci/has_status.rb
index c1299e3d468..8d715279da8 100644
--- a/app/models/concerns/ci/has_status.rb
+++ b/app/models/concerns/ci/has_status.rb
@@ -95,6 +95,7 @@ module Ci
scope :failed_or_canceled, -> { with_status(:failed, :canceled) }
scope :complete, -> { with_status(completed_statuses) }
scope :incomplete, -> { without_statuses(completed_statuses) }
+ scope :waiting_for_resource_or_upcoming, -> { with_status(:created, :scheduled, :waiting_for_resource) }
scope :cancelable, -> do
where(status: [:running, :waiting_for_resource, :preparing, :pending, :created, :scheduled])
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index ec86746ae54..344f5aa4cd5 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -20,6 +20,7 @@ module Ci
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true
+ delegate :runner_features, to: :metadata, prefix: false, allow_nil: false
before_create :ensure_metadata
end
diff --git a/app/models/concerns/enums/ci/commit_status.rb b/app/models/concerns/enums/ci/commit_status.rb
index 7f46e44697e..1b4cc14f4a2 100644
--- a/app/models/concerns/enums/ci/commit_status.rb
+++ b/app/models/concerns/enums/ci/commit_status.rb
@@ -27,6 +27,7 @@ module Enums
no_matching_runner: 18, # not used anymore, but cannot be deleted because of old data
trace_size_exceeded: 19,
builds_disabled: 20,
+ environment_creation_failure: 21,
insufficient_bridge_permissions: 1_001,
downstream_bridge_project_not_found: 1_002,
invalid_bridge_trigger: 1_003,
diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb
index 9218ba47d20..d614d6c4584 100644
--- a/app/models/concerns/has_repository.rb
+++ b/app/models/concerns/has_repository.rb
@@ -72,12 +72,10 @@ module HasRepository
end
def default_branch
- @default_branch ||= repository.root_ref || default_branch_from_preferences
+ @default_branch ||= repository.empty? ? default_branch_from_preferences : repository.root_ref
end
def default_branch_from_preferences
- return unless empty_repo?
-
(default_branch_from_group_preferences || Gitlab::CurrentSettings.default_branch_name).presence
end
diff --git a/app/models/concerns/integrations/has_data_fields.rb b/app/models/concerns/integrations/has_data_fields.rb
index 1709b56080e..25a1d855119 100644
--- a/app/models/concerns/integrations/has_data_fields.rb
+++ b/app/models/concerns/integrations/has_data_fields.rb
@@ -45,7 +45,6 @@ module Integrations
included do
has_one :issue_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :service_id, class_name: 'Integrations::IssueTrackerData'
has_one :jira_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :service_id, class_name: 'Integrations::JiraTrackerData'
- has_one :open_project_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :service_id, class_name: 'Integrations::OpenProjectTrackerData'
has_one :zentao_tracker_data, autosave: true, inverse_of: :integration, foreign_key: :integration_id, class_name: 'Integrations::ZentaoTrackerData'
def data_fields
diff --git a/app/models/concerns/issue_available_features.rb b/app/models/concerns/issue_available_features.rb
index 933e8b5f687..209456f8b67 100644
--- a/app/models/concerns/issue_available_features.rb
+++ b/app/models/concerns/issue_available_features.rb
@@ -12,7 +12,8 @@ module IssueAvailableFeatures
{
assignee: %w(issue incident),
confidentiality: %w(issue incident),
- time_tracking: %w(issue incident)
+ time_tracking: %w(issue incident),
+ move_and_clone: %w(issue incident)
}.with_indifferent_access
end
end
diff --git a/app/models/concerns/packages/debian/distribution.rb b/app/models/concerns/packages/debian/distribution.rb
index 196bec04be6..ff52769fce8 100644
--- a/app/models/concerns/packages/debian/distribution.rb
+++ b/app/models/concerns/packages/debian/distribution.rb
@@ -96,18 +96,8 @@ module Packages
architectures.pluck(:name).sort
end
- def needs_update?
- !file.exists? || time_duration_expired?
- end
-
private
- def time_duration_expired?
- return false unless valid_time_duration_seconds.present?
-
- updated_at + valid_time_duration_seconds.seconds + 6.hours < Time.current
- end
-
def unique_codename_and_suite
errors.add(:codename, _('has already been taken as Suite')) if codename_exists_as_suite?
errors.add(:suite, _('has already been taken as Codename')) if suite_exists_as_codename?
diff --git a/app/models/concerns/restricted_signup.rb b/app/models/concerns/restricted_signup.rb
index 587f8c35ff7..cf97be21165 100644
--- a/app/models/concerns/restricted_signup.rb
+++ b/app/models/concerns/restricted_signup.rb
@@ -7,15 +7,49 @@ module RestrictedSignup
def validate_admin_signup_restrictions(email)
return if allowed_domain?(email)
+ error_type = fetch_error_type(email)
+
+ return unless error_type.present?
+
+ [
+ signup_email_invalid_message,
+ error_message[created_by_key][error_type]
+ ].join(' ')
+ end
+
+ def fetch_error_type(email)
if allowlist_present?
- return _('domain is not authorized for sign-up.')
+ :allowlist
elsif denied_domain?(email)
- return _('is not from an allowed domain.')
+ :denylist
elsif restricted_email?(email)
- return _('is not allowed. Try again with a different email address, or contact your GitLab admin.')
+ :restricted
end
+ end
+
+ def error_message
+ {
+ admin: {
+ allowlist: html_escape_once(_("Go to the 'Admin area &gt; Sign-up restrictions', and check 'Allowed domains for sign-ups'.")).html_safe,
+ denylist: html_escape_once(_("Go to the 'Admin area &gt; Sign-up restrictions', and check the 'Domain denylist'.")).html_safe,
+ restricted: html_escape_once(_("Go to the 'Admin area &gt; Sign-up restrictions', and check 'Email restrictions for sign-ups'.")).html_safe,
+ group_setting: html_escape_once(_("Go to the group’s 'Settings &gt; General' page, and check 'Restrict membership by email domain'.")).html_safe
+ },
+ nonadmin: {
+ allowlist: error_nonadmin,
+ denylist: error_nonadmin,
+ restricted: error_nonadmin,
+ group_setting: error_nonadmin
+ }
+ }
+ end
+
+ def error_nonadmin
+ _("Check with your administrator.")
+ end
- nil
+ def created_by_key
+ created_by&.can_admin_all_resources? ? :admin : :nonadmin
end
def denied_domain?(email)
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 847abdc1b6d..f382b3624ed 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -41,7 +41,7 @@ module Routable
has_one :route, as: :source, autosave: true, dependent: :destroy, inverse_of: :source # rubocop:disable Cop/ActiveRecordDependent
has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- validates :route, presence: true
+ validates :route, presence: true, unless: -> { is_a?(Namespaces::ProjectNamespace) }
scope :with_route, -> { includes(:route) }
@@ -185,6 +185,7 @@ module Routable
def prepare_route
return unless full_path_changed? || full_name_changed?
+ return if is_a?(Namespaces::ProjectNamespace)
route || build_route(source: self)
route.path = build_full_path
diff --git a/app/models/concerns/ttl_expirable.rb b/app/models/concerns/ttl_expirable.rb
new file mode 100644
index 00000000000..00abe0a06e6
--- /dev/null
+++ b/app/models/concerns/ttl_expirable.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module TtlExpirable
+ extend ActiveSupport::Concern
+
+ included do
+ validates :status, presence: true
+
+ enum status: { default: 0, expired: 1, processing: 2, error: 3 }
+
+ scope :updated_before, ->(number_of_days) { where("updated_at <= ?", Time.zone.now - number_of_days.days) }
+ scope :active, -> { where(status: :default) }
+
+ scope :lock_next_by, ->(sort) do
+ order(sort)
+ .limit(1)
+ .lock('FOR UPDATE SKIP LOCKED')
+ end
+ end
+end
diff --git a/app/models/concerns/vulnerability_finding_helpers.rb b/app/models/concerns/vulnerability_finding_helpers.rb
index a656856487d..7f96b3901f1 100644
--- a/app/models/concerns/vulnerability_finding_helpers.rb
+++ b/app/models/concerns/vulnerability_finding_helpers.rb
@@ -2,6 +2,15 @@
module VulnerabilityFindingHelpers
extend ActiveSupport::Concern
+
+ # Manually resolvable report types cannot be considered fixed once removed from the
+ # target branch due to requiring active triage, such as rotation of an exposed token.
+ REPORT_TYPES_REQUIRING_MANUAL_RESOLUTION = %w[secret_detection].freeze
+
+ def requires_manual_resolution?
+ REPORT_TYPES_REQUIRING_MANUAL_RESOLUTION.include?(report_type)
+ end
+
def matches_signatures(other_signatures, other_uuid)
other_signature_types = other_signatures.index_by(&:algorithm_type)
diff --git a/app/models/container_expiration_policy.rb b/app/models/container_expiration_policy.rb
index 9bacd9a0edf..aecb47f7a03 100644
--- a/app/models/container_expiration_policy.rb
+++ b/app/models/container_expiration_policy.rb
@@ -74,6 +74,7 @@ class ContainerExpirationPolicy < ApplicationRecord
'7d': _('%{days} days until tags are automatically removed') % { days: 7 },
'14d': _('%{days} days until tags are automatically removed') % { days: 14 },
'30d': _('%{days} days until tags are automatically removed') % { days: 30 },
+ '60d': _('%{days} days until tags are automatically removed') % { days: 60 },
'90d': _('%{days} days until tags are automatically removed') % { days: 90 }
}
end
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index aea48a5ec20..ecdac64b31b 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -5,7 +5,7 @@ class CustomEmoji < ApplicationRecord
belongs_to :namespace, inverse_of: :custom_emoji
- belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
+ belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'namespace_id'
belongs_to :creator, class_name: "User", inverse_of: :created_custom_emoji
# For now only external emoji are supported. See https://gitlab.com/gitlab-org/gitlab/-/issues/230467
diff --git a/app/models/customer_relations/contact.rb b/app/models/customer_relations/contact.rb
index aaa7e2ae175..c632f8e2efa 100644
--- a/app/models/customer_relations/contact.rb
+++ b/app/models/customer_relations/contact.rb
@@ -5,8 +5,9 @@ class CustomerRelations::Contact < ApplicationRecord
self.table_name = "customer_relations_contacts"
- belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'group_id'
+ belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'group_id'
belongs_to :organization, optional: true
+ has_and_belongs_to_many :issues, join_table: :issue_customer_relations_contacts # rubocop: disable Rails/HasAndBelongsToMany
strip_attributes! :phone, :first_name, :last_name
diff --git a/app/models/customer_relations/organization.rb b/app/models/customer_relations/organization.rb
index a18d3ab8148..c206d1e05f5 100644
--- a/app/models/customer_relations/organization.rb
+++ b/app/models/customer_relations/organization.rb
@@ -5,7 +5,7 @@ class CustomerRelations::Organization < ApplicationRecord
self.table_name = "customer_relations_organizations"
- belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'group_id'
+ belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'group_id'
strip_attributes! :name
diff --git a/app/models/dependency_proxy/blob.rb b/app/models/dependency_proxy/blob.rb
index 5de6b1cf28f..7ca15652586 100644
--- a/app/models/dependency_proxy/blob.rb
+++ b/app/models/dependency_proxy/blob.rb
@@ -2,15 +2,14 @@
class DependencyProxy::Blob < ApplicationRecord
include FileStoreMounter
+ include TtlExpirable
+ include EachBatch
belongs_to :group
validates :group, presence: true
validates :file, presence: true
validates :file_name, presence: true
- validates :status, presence: true
-
- enum status: { default: 0, expired: 1 }
mount_file_store_uploader DependencyProxy::FileUploader
diff --git a/app/models/dependency_proxy/image_ttl_group_policy.rb b/app/models/dependency_proxy/image_ttl_group_policy.rb
index 5a1b8cb8f1f..0dfb298a39e 100644
--- a/app/models/dependency_proxy/image_ttl_group_policy.rb
+++ b/app/models/dependency_proxy/image_ttl_group_policy.rb
@@ -8,4 +8,6 @@ class DependencyProxy::ImageTtlGroupPolicy < ApplicationRecord
validates :group, presence: true
validates :enabled, inclusion: { in: [true, false] }
validates :ttl, numericality: { greater_than: 0 }, allow_nil: true
+
+ scope :enabled, -> { where(enabled: true) }
end
diff --git a/app/models/dependency_proxy/manifest.rb b/app/models/dependency_proxy/manifest.rb
index 15e5137b50a..b83047efe54 100644
--- a/app/models/dependency_proxy/manifest.rb
+++ b/app/models/dependency_proxy/manifest.rb
@@ -2,6 +2,8 @@
class DependencyProxy::Manifest < ApplicationRecord
include FileStoreMounter
+ include TtlExpirable
+ include EachBatch
belongs_to :group
@@ -9,9 +11,6 @@ class DependencyProxy::Manifest < ApplicationRecord
validates :file, presence: true
validates :file_name, presence: true
validates :digest, presence: true
- validates :status, presence: true
-
- enum status: { default: 0, expired: 1 }
mount_file_store_uploader DependencyProxy::FileUploader
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 4a690ccc67e..f91700f764b 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -10,7 +10,8 @@ class Deployment < ApplicationRecord
include FastDestroyAll
include IgnorableColumns
- ignore_column :deployable_id_convert_to_bigint, remove_with: '14.2', remove_after: '2021-08-22'
+ StatusUpdateError = Class.new(StandardError)
+ StatusSyncError = Class.new(StandardError)
belongs_to :project, required: true
belongs_to :environment, required: true
@@ -48,7 +49,6 @@ class Deployment < ApplicationRecord
scope :stoppable, -> { where.not(on_stop: nil).where.not(deployable_id: nil).success }
scope :active, -> { where(status: %i[created running]) }
scope :older_than, -> (deployment) { where('deployments.id < ?', deployment.id) }
- scope :with_deployable, -> { joins('INNER JOIN ci_builds ON ci_builds.id = deployments.deployable_id').preload(:deployable) }
scope :with_api_entity_associations, -> { preload({ deployable: { runner: [], tags: [], user: [], job_artifacts_archive: [] } }) }
scope :finished_after, ->(date) { where('finished_at >= ?', date) }
@@ -150,6 +150,16 @@ class Deployment < ApplicationRecord
success.find_by!(iid: iid)
end
+ # It should be used with caution especially on chaining.
+ # Fetching any unbounded or large intermediate dataset could lead to loading too many IDs into memory.
+ # See: https://docs.gitlab.com/ee/development/database/multiple_databases.html#use-disable_joins-for-has_one-or-has_many-through-relations
+ # For safety we default limit to fetch not more than 1000 records.
+ def self.builds(limit = 1000)
+ deployable_ids = where.not(deployable_id: nil).limit(limit).pluck(:deployable_id)
+
+ Ci::Build.where(id: deployable_ids)
+ end
+
class << self
##
# FastDestroyAll concerns
@@ -305,20 +315,23 @@ class Deployment < ApplicationRecord
# Changes the status of a deployment and triggers the corresponding state
# machine events.
def update_status(status)
- case status
- when 'running'
- run
- when 'success'
- succeed
- when 'failed'
- drop
- when 'canceled'
- cancel
- when 'skipped'
- skip
- else
- raise ArgumentError, "The status #{status.inspect} is invalid"
- end
+ update_status!(status)
+ rescue StandardError => e
+ Gitlab::ErrorTracking.track_exception(
+ StatusUpdateError.new(e.message), deployment_id: self.id)
+
+ false
+ end
+
+ def sync_status_with(build)
+ return false unless ::Deployment.statuses.include?(build.status)
+
+ update_status!(build.status)
+ rescue StandardError => e
+ Gitlab::ErrorTracking.track_exception(
+ StatusSyncError.new(e.message), deployment_id: self.id, build_id: build.id)
+
+ false
end
def valid_sha
@@ -346,6 +359,23 @@ class Deployment < ApplicationRecord
private
+ def update_status!(status)
+ case status
+ when 'running'
+ run!
+ when 'success'
+ succeed!
+ when 'failed'
+ drop!
+ when 'canceled'
+ cancel!
+ when 'skipped'
+ skip!
+ else
+ raise ArgumentError, "The status #{status.inspect} is invalid"
+ end
+ end
+
def legacy_finished_at
self.created_at if success? && !read_attribute(:finished_at)
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 48522a23068..31ab426728b 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -28,8 +28,8 @@ class Environment < ApplicationRecord
has_one :last_deployment, -> { success.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment
has_one :last_visible_deployment, -> { visible.distinct_on_environment }, inverse_of: :environment, class_name: 'Deployment'
- has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus', disable_joins: -> { ::Feature.enabled?(:environment_last_visible_pipeline_disable_joins, default_enabled: :yaml) }
- has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline', disable_joins: -> { ::Feature.enabled?(:environment_last_visible_pipeline_disable_joins, default_enabled: :yaml) }
+ has_one :last_visible_deployable, through: :last_visible_deployment, source: 'deployable', source_type: 'CommitStatus', disable_joins: true
+ has_one :last_visible_pipeline, through: :last_visible_deployable, source: 'pipeline', disable_joins: true
has_one :upcoming_deployment, -> { running.distinct_on_environment }, class_name: 'Deployment', inverse_of: :environment
has_one :latest_opened_most_severe_alert, -> { order_severity_with_open_prometheus_alert }, class_name: 'AlertManagement::Alert', inverse_of: :environment
@@ -198,14 +198,14 @@ class Environment < ApplicationRecord
# Overriding association
def last_visible_deployable
- return super if association_cached?(:last_visible_deployable) || ::Feature.disabled?(:environment_last_visible_pipeline_disable_joins, default_enabled: :yaml)
+ return super if association_cached?(:last_visible_deployable)
last_visible_deployment&.deployable
end
# Overriding association
def last_visible_pipeline
- return super if association_cached?(:last_visible_pipeline) || ::Feature.disabled?(:environment_last_visible_pipeline_disable_joins, default_enabled: :yaml)
+ return super if association_cached?(:last_visible_pipeline)
last_visible_deployable&.pipeline
end
@@ -260,10 +260,9 @@ class Environment < ApplicationRecord
end
def cancel_deployment_jobs!
- jobs = active_deployments.with_deployable
- jobs.each do |deployment|
- Gitlab::OptimisticLocking.retry_lock(deployment.deployable, name: 'environment_cancel_deployment_jobs') do |deployable|
- deployable.cancel! if deployable&.cancelable?
+ active_deployments.builds.each do |build|
+ Gitlab::OptimisticLocking.retry_lock(build, name: 'environment_cancel_deployment_jobs') do |build|
+ build.cancel! if build&.cancelable?
end
rescue StandardError => e
Gitlab::ErrorTracking.track_exception(e, environment_id: id, deployment_id: deployment.id)
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
index 3be7af2e4bf..07c0983f239 100644
--- a/app/models/environment_status.rb
+++ b/app/models/environment_status.rb
@@ -100,13 +100,11 @@ class EnvironmentStatus
def self.build_environments_status(mr, user, pipeline)
return [] unless pipeline
- ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/340781') do
- pipeline.environments_in_self_and_descendants.includes(:project).available.map do |environment|
- next unless Ability.allowed?(user, :read_environment, environment)
+ pipeline.environments_in_self_and_descendants.includes(:project).available.map do |environment|
+ next unless Ability.allowed?(user, :read_environment, environment)
- EnvironmentStatus.new(pipeline.project, environment, mr, pipeline.sha)
- end.compact
- end
+ EnvironmentStatus.new(pipeline.project, environment, mr, pipeline.sha)
+ end.compact
end
private_class_method :build_environments_status
end
diff --git a/app/models/error_tracking/error.rb b/app/models/error_tracking/error.rb
index 39ecc487806..2d6a4694def 100644
--- a/app/models/error_tracking/error.rb
+++ b/app/models/error_tracking/error.rb
@@ -7,6 +7,14 @@ class ErrorTracking::Error < ApplicationRecord
has_many :events, class_name: 'ErrorTracking::ErrorEvent'
+ has_one :first_event,
+ -> { order(id: :asc) },
+ class_name: 'ErrorTracking::ErrorEvent'
+
+ has_one :last_event,
+ -> { order(id: :desc) },
+ class_name: 'ErrorTracking::ErrorEvent'
+
scope :for_status, -> (status) { where(status: status) }
validates :project, presence: true
@@ -90,7 +98,10 @@ class ErrorTracking::Error < ApplicationRecord
status: status,
tags: { level: nil, logger: nil },
external_url: external_url,
- external_base_url: external_base_url
+ external_base_url: external_base_url,
+ integrated: true,
+ first_release_version: first_event&.release,
+ last_release_version: last_event&.release
)
end
@@ -106,6 +117,6 @@ class ErrorTracking::Error < ApplicationRecord
# For compatibility with sentry integration
def external_base_url
- Gitlab::Routing.url_helpers.root_url
+ Gitlab::Routing.url_helpers.project_url(project)
end
end
diff --git a/app/models/error_tracking/error_event.rb b/app/models/error_tracking/error_event.rb
index 4de13de7e2e..686518a39fb 100644
--- a/app/models/error_tracking/error_event.rb
+++ b/app/models/error_tracking/error_event.rb
@@ -22,6 +22,10 @@ class ErrorTracking::ErrorEvent < ApplicationRecord
)
end
+ def release
+ payload.dig('release')
+ end
+
private
def build_stacktrace
diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
index dd5ce9f7387..25f812645b1 100644
--- a/app/models/error_tracking/project_error_tracking_setting.rb
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -46,6 +46,11 @@ module ErrorTracking
after_save :clear_reactive_cache!
+ # When a user enables the integrated error tracking
+ # we want to immediately provide them with a first
+ # working client key so they have a DSN for Sentry SDK.
+ after_save :create_client_key!
+
def sentry_enabled
enabled && !integrated_client?
end
@@ -54,6 +59,12 @@ module ErrorTracking
integrated
end
+ def gitlab_dsn
+ strong_memoize(:gitlab_dsn) do
+ client_key&.sentry_dsn
+ end
+ end
+
def api_url=(value)
super
clear_memoization(:api_url_slugs)
@@ -236,5 +247,19 @@ module ErrorTracking
errors.add(:project, 'is a required field')
end
end
+
+ def client_key
+ # Project can have multiple client keys.
+ # However for UI simplicity we render the first active one for user.
+ # In future we should make it possible to manage client keys from UI.
+ # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/329596
+ project.error_tracking_client_keys.active.first
+ end
+
+ def create_client_key!
+ if enabled? && integrated_client? && !client_key
+ project.error_tracking_client_keys.create!
+ end
+ end
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index a667a908707..c5e119451e3 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -192,9 +192,15 @@ class Group < Namespace
# Returns the ids of the passed group models where the `emails_disabled`
# column is set to true anywhere in the ancestor hierarchy.
def ids_with_disabled_email(groups)
- innner_query = Gitlab::ObjectHierarchy
- .new(Group.where('id = namespaces_with_emails_disabled.id'))
- .base_and_ancestors
+ inner_groups = Group.where('id = namespaces_with_emails_disabled.id')
+
+ inner_ancestors = if Feature.enabled?(:linear_group_ancestor_scopes, default_enabled: :yaml)
+ inner_groups.self_and_ancestors
+ else
+ Gitlab::ObjectHierarchy.new(inner_groups).base_and_ancestors
+ end
+
+ inner_query = inner_ancestors
.where(emails_disabled: true)
.select('1')
.limit(1)
@@ -202,7 +208,7 @@ class Group < Namespace
group_ids = Namespace
.from('(SELECT * FROM namespaces) as namespaces_with_emails_disabled')
.where(namespaces_with_emails_disabled: { id: groups })
- .where('EXISTS (?)', innner_query)
+ .where('EXISTS (?)', inner_query)
.pluck(:id)
Set.new(group_ids)
@@ -701,9 +707,9 @@ class Group < Namespace
raise ArgumentError unless SHARED_RUNNERS_SETTINGS.include?(state)
case state
- when 'disabled_and_unoverridable' then disable_shared_runners! # also disallows override
- when 'disabled_with_override' then disable_shared_runners_and_allow_override!
- when 'enabled' then enable_shared_runners! # set both to true
+ when SR_DISABLED_AND_UNOVERRIDABLE then disable_shared_runners! # also disallows override
+ when SR_DISABLED_WITH_OVERRIDE then disable_shared_runners_and_allow_override!
+ when SR_ENABLED then enable_shared_runners! # set both to true
end
end
diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb
index 9565dae08b5..0bf9e805aa8 100644
--- a/app/models/instance_configuration.rb
+++ b/app/models/instance_configuration.rb
@@ -22,7 +22,12 @@ class InstanceConfiguration
private
def ssh_algorithms_hashes
- SSH_ALGORITHMS.map { |algo| ssh_algorithm_hashes(algo) }.compact
+ SSH_ALGORITHMS.select { |algo| ssh_algorithm_enabled?(algo) }.map { |algo| ssh_algorithm_hashes(algo) }.compact
+ end
+
+ def ssh_algorithm_enabled?(algorithm)
+ algorithm_key_restriction = application_settings["#{algorithm.downcase}_key_restriction"]
+ algorithm_key_restriction.nil? || algorithm_key_restriction != ApplicationSetting::FORBIDDEN_KEY_VALUE
end
def host
@@ -98,6 +103,11 @@ class InstanceConfiguration
requests_per_period: application_settings[:throttle_authenticated_packages_api_requests_per_period],
period_in_seconds: application_settings[:throttle_authenticated_packages_api_period_in_seconds]
},
+ authenticated_git_lfs_api: {
+ enabled: application_settings[:throttle_authenticated_git_lfs_enabled],
+ requests_per_period: application_settings[:throttle_authenticated_git_lfs_requests_per_period],
+ period_in_seconds: application_settings[:throttle_authenticated_git_lfs_period_in_seconds]
+ },
issue_creation: application_setting_limit_per_minute(:issues_create_limit),
note_creation: application_setting_limit_per_minute(:notes_create_limit),
project_export: application_setting_limit_per_minute(:project_export_limit),
diff --git a/app/models/integrations/open_project.rb b/app/models/integrations/open_project.rb
deleted file mode 100644
index e4cfb24151a..00000000000
--- a/app/models/integrations/open_project.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-module Integrations
- class OpenProject < BaseIssueTracker
- validates :url, public_url: true, presence: true, if: :activated?
- validates :api_url, public_url: true, allow_blank: true, if: :activated?
- validates :token, presence: true, if: :activated?
- validates :project_identifier_code, presence: true, if: :activated?
-
- data_field :url, :api_url, :token, :closed_status_id, :project_identifier_code
-
- def data_fields
- open_project_tracker_data || self.build_open_project_tracker_data
- end
-
- def self.to_param
- 'open_project'
- end
- end
-end
diff --git a/app/models/integrations/open_project_tracker_data.rb b/app/models/integrations/open_project_tracker_data.rb
deleted file mode 100644
index b3f2618b94f..00000000000
--- a/app/models/integrations/open_project_tracker_data.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Integrations
- class OpenProjectTrackerData < ApplicationRecord
- include BaseDataFields
-
- # When the Open Project is fresh installed, the default closed status id is "13" based on current version: v8.
- DEFAULT_CLOSED_STATUS_ID = "13"
-
- attr_encrypted :url, encryption_options
- attr_encrypted :api_url, encryption_options
- attr_encrypted :token, encryption_options
-
- def closed_status_id
- super || DEFAULT_CLOSED_STATUS_ID
- end
- end
-end
diff --git a/app/models/integrations/unify_circuit.rb b/app/models/integrations/unify_circuit.rb
index ad6a9164d00..e3e180ae959 100644
--- a/app/models/integrations/unify_circuit.rb
+++ b/app/models/integrations/unify_circuit.rb
@@ -15,13 +15,8 @@ module Integrations
end
def help
- 'This service sends notifications about projects events to a Unify Circuit conversation.<br />
- To set up this service:
- <ol>
- <li><a href="https://www.circuit.com/unifyportalfaqdetail?articleId=164448" target="_blank" rel="noopener noreferrer">Set up an incoming webhook for your conversation</a>. All notifications will come to this conversation.</li>
- <li>Paste the <strong>Webhook URL</strong> into the field below.</li>
- <li>Select events below to enable notifications.</li>
- </ol>'
+ docs_link = ActionController::Base.helpers.link_to _('How do I set up this service?'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/unify_circuit'), target: '_blank', rel: 'noopener noreferrer'
+ s_('Integrations|Send notifications about project events to a Unify Circuit conversation. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def event_field(event)
@@ -37,7 +32,7 @@ module Integrations
def default_fields
[
- { type: 'text', name: 'webhook', placeholder: "e.g. https://circuit.com/rest/v2/webhooks/incoming/…", required: true },
+ { type: 'text', name: 'webhook', placeholder: "https://yourcircuit.com/rest/v2/webhooks/incoming/…", required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
]
diff --git a/app/models/issue.rb b/app/models/issue.rb
index e0b0c352c22..9c568414ec2 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -81,6 +81,7 @@ class Issue < ApplicationRecord
has_and_belongs_to_many :self_managed_prometheus_alert_events, join_table: :issues_self_managed_prometheus_alert_events # rubocop: disable Rails/HasAndBelongsToMany
has_and_belongs_to_many :prometheus_alert_events, join_table: :issues_prometheus_alert_events # rubocop: disable Rails/HasAndBelongsToMany
has_many :prometheus_alerts, through: :prometheus_alert_events
+ has_and_belongs_to_many :customer_relations_contacts, join_table: :issue_customer_relations_contacts, class_name: 'CustomerRelations::Contact' # rubocop: disable Rails/HasAndBelongsToMany
accepts_nested_attributes_for :issuable_severity, update_only: true
accepts_nested_attributes_for :sentry_issue
@@ -107,8 +108,6 @@ class Issue < ApplicationRecord
scope :order_due_date_asc, -> { reorder(::Gitlab::Database.nulls_last_order('due_date', 'ASC')) }
scope :order_due_date_desc, -> { reorder(::Gitlab::Database.nulls_last_order('due_date', 'DESC')) }
scope :order_closest_future_date, -> { reorder(Arel.sql('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC')) }
- scope :order_relative_position_asc, -> { reorder(::Gitlab::Database.nulls_last_order('relative_position', 'ASC')) }
- scope :order_relative_position_desc, -> { reorder(::Gitlab::Database.nulls_first_order('relative_position', 'DESC')) }
scope :order_closed_date_desc, -> { reorder(closed_at: :desc) }
scope :order_created_at_desc, -> { reorder(created_at: :desc) }
scope :order_severity_asc, -> { includes(:issuable_severity).order('issuable_severities.severity ASC NULLS FIRST') }
@@ -127,6 +126,7 @@ class Issue < ApplicationRecord
project: [:route, { namespace: :route }])
}
scope :with_issue_type, ->(types) { where(issue_type: types) }
+ scope :without_issue_type, ->(types) { where.not(issue_type: types) }
scope :public_only, -> {
without_hidden.where(confidential: false)
@@ -166,6 +166,8 @@ class Issue < ApplicationRecord
scope :by_project_id_and_iid, ->(composites) do
where_composite(%i[project_id iid], composites)
end
+ scope :with_null_relative_position, -> { where(relative_position: nil) }
+ scope :with_non_null_relative_position, -> { where.not(relative_position: nil) }
after_commit :expire_etag_cache, unless: :importing?
after_save :ensure_metrics, unless: :importing?
@@ -266,8 +268,8 @@ class Issue < ApplicationRecord
'due_date' => -> { order_due_date_asc.with_order_id_desc },
'due_date_asc' => -> { order_due_date_asc.with_order_id_desc },
'due_date_desc' => -> { order_due_date_desc.with_order_id_desc },
- 'relative_position' => -> { order_relative_position_asc.with_order_id_desc },
- 'relative_position_asc' => -> { order_relative_position_asc.with_order_id_desc }
+ 'relative_position' => -> { order_by_relative_position },
+ 'relative_position_asc' => -> { order_by_relative_position }
}
)
end
@@ -277,7 +279,7 @@ class Issue < ApplicationRecord
when 'closest_future_date', 'closest_future_date_asc' then order_closest_future_date
when 'due_date', 'due_date_asc' then order_due_date_asc.with_order_id_desc
when 'due_date_desc' then order_due_date_desc.with_order_id_desc
- when 'relative_position', 'relative_position_asc' then order_relative_position_asc.with_order_id_desc
+ when 'relative_position', 'relative_position_asc' then order_by_relative_position
when 'severity_asc' then order_severity_asc.with_order_id_desc
when 'severity_desc' then order_severity_desc.with_order_id_desc
else
@@ -285,13 +287,8 @@ class Issue < ApplicationRecord
end
end
- # `with_cte` argument allows sorting when using CTE queries and prevents
- # errors in postgres when using CTE search optimisation
- def self.order_by_position_and_priority(with_cte: false)
- order = Gitlab::Pagination::Keyset::Order.build([column_order_relative_position, column_order_highest_priority, column_order_id_desc])
-
- order_labels_priority(with_cte: with_cte)
- .reorder(order)
+ def self.order_by_relative_position
+ reorder(Gitlab::Pagination::Keyset::Order.build([column_order_relative_position, column_order_id_asc]))
end
def self.column_order_relative_position
@@ -306,25 +303,6 @@ class Issue < ApplicationRecord
)
end
- def self.column_order_highest_priority
- Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
- attribute_name: 'highest_priority',
- column_expression: Arel.sql('highest_priorities.label_priority'),
- order_expression: Gitlab::Database.nulls_last_order('highest_priorities.label_priority', 'ASC'),
- reversed_order_expression: Gitlab::Database.nulls_last_order('highest_priorities.label_priority', 'DESC'),
- order_direction: :asc,
- nullable: :nulls_last,
- distinct: false
- )
- end
-
- def self.column_order_id_desc
- Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
- attribute_name: 'id',
- order_expression: arel_table[:id].desc
- )
- end
-
def self.column_order_id_asc
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'id',
@@ -541,6 +519,10 @@ class Issue < ApplicationRecord
issue_type_supports?(:time_tracking)
end
+ def supports_move_and_clone?
+ issue_type_supports?(:move_and_clone)
+ end
+
def email_participants_emails
issue_email_participants.pluck(:email)
end
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 53e7d52c558..9765ac6f2e9 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -49,7 +49,7 @@ class LfsObject < ApplicationRecord
end
def self.calculate_oid(path)
- self.hexdigest(path)
+ self.sha256_hexdigest(path)
end
end
diff --git a/app/models/loose_foreign_keys/deleted_record.rb b/app/models/loose_foreign_keys/deleted_record.rb
index a39d88b2e49..ca5a2800a03 100644
--- a/app/models/loose_foreign_keys/deleted_record.rb
+++ b/app/models/loose_foreign_keys/deleted_record.rb
@@ -2,48 +2,4 @@
class LooseForeignKeys::DeletedRecord < ApplicationRecord
extend SuppressCompositePrimaryKeyWarning
- include PartitionedTable
-
- partitioned_by :created_at, strategy: :monthly, retain_for: 3.months, retain_non_empty_partitions: true
-
- scope :ordered_by_primary_keys, -> { order(:created_at, :deleted_table_name, :deleted_table_primary_key_value) }
-
- def self.load_batch(batch_size)
- ordered_by_primary_keys
- .limit(batch_size)
- .to_a
- end
-
- # Because the table has composite primary keys, the delete_all or delete methods are not going to work.
- # This method implements deletion that benefits from the primary key index, example:
- #
- # > DELETE
- # > FROM "loose_foreign_keys_deleted_records"
- # > WHERE (created_at,
- # > deleted_table_name,
- # > deleted_table_primary_key_value) IN
- # > (SELECT created_at::TIMESTAMP WITH TIME ZONE,
- # > deleted_table_name,
- # > deleted_table_primary_key_value
- # > FROM (VALUES (LIST_OF_VALUES)) AS primary_key_values (created_at, deleted_table_name, deleted_table_primary_key_value))
- def self.delete_records(records)
- values = records.pluck(:created_at, :deleted_table_name, :deleted_table_primary_key_value)
-
- primary_keys = connection.primary_keys(table_name).join(', ')
-
- primary_keys_with_type_cast = [
- Arel.sql('created_at::timestamp with time zone'),
- Arel.sql('deleted_table_name'),
- Arel.sql('deleted_table_primary_key_value')
- ]
-
- value_list = Arel::Nodes::ValuesList.new(values)
-
- # (SELECT primary keys FROM VALUES)
- inner_query = Arel::SelectManager.new
- inner_query.from("#{Arel::Nodes::Grouping.new([value_list]).as('primary_key_values').to_sql} (#{primary_keys})")
- inner_query.projections = primary_keys_with_type_cast
-
- where(Arel::Nodes::Grouping.new([Arel.sql(primary_keys)]).in(inner_query)).delete_all
- end
end
diff --git a/app/models/member.rb b/app/models/member.rb
index beb4c05f2a6..21fd4aebd7b 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -50,6 +50,11 @@ class Member < ApplicationRecord
},
if: :project_bot?
+ scope :with_invited_user_state, -> do
+ joins('LEFT JOIN users as invited_user ON invited_user.email = members.invite_email')
+ .select('members.*', 'invited_user.state as invited_user_state')
+ end
+
scope :in_hierarchy, ->(source) do
groups = source.root_ancestor.self_and_descendants
group_members = Member.default_scoped.where(source: groups)
@@ -178,7 +183,13 @@ class Member < ApplicationRecord
after_destroy :post_destroy_hook, unless: :pending?, if: :hook_prerequisites_met?
after_save :log_invitation_token_cleanup
- after_commit :refresh_member_authorized_projects, unless: :importing?
+ after_commit on: [:create, :update], unless: :importing? do
+ refresh_member_authorized_projects(blocking: true)
+ end
+
+ after_commit on: [:destroy], unless: :importing? do
+ refresh_member_authorized_projects(blocking: false)
+ end
default_value_for :notification_level, NotificationSetting.levels[:global]
@@ -395,8 +406,8 @@ class Member < ApplicationRecord
# transaction has been committed, resulting in the job either throwing an
# error or not doing any meaningful work.
# rubocop: disable CodeReuse/ServiceClass
- def refresh_member_authorized_projects
- UserProjectAccessChangedService.new(user_id).execute
+ def refresh_member_authorized_projects(blocking:)
+ UserProjectAccessChangedService.new(user_id).execute(blocking: blocking)
end
# rubocop: enable CodeReuse/ServiceClass
@@ -442,6 +453,14 @@ class Member < ApplicationRecord
errors.add(:user, error) if error
end
+ def signup_email_invalid_message
+ if source_type == 'Project'
+ _("is not allowed for this project.")
+ else
+ _("is not allowed for this group.")
+ end
+ end
+
def update_highest_role?
return unless user_id.present?
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index a13133c90e9..9062a405218 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -43,15 +43,17 @@ class GroupMember < Member
# Because source_type is `Namespace`...
def real_source_type
- 'Group'
+ Group.sti_name
end
def notifiable_options
{ group: group }
end
+ private
+
override :refresh_member_authorized_projects
- def refresh_member_authorized_projects
+ def refresh_member_authorized_projects(blocking:)
# Here, `destroyed_by_association` will be present if the
# GroupMember is being destroyed due to the `dependent: :destroy`
# callback on Group. In this case, there is no need to refresh the
@@ -63,8 +65,6 @@ class GroupMember < Member
super
end
- private
-
def access_level_inclusion
return if access_level.in?(Gitlab::Access.all_values)
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 72cb831cc88..eec46b3493e 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -90,24 +90,28 @@ class ProjectMember < Member
{ project: project }
end
+ private
+
override :refresh_member_authorized_projects
- def refresh_member_authorized_projects
+ def refresh_member_authorized_projects(blocking:)
return super unless Feature.enabled?(:specialized_service_for_project_member_auth_refresh)
return unless user
# rubocop:disable CodeReuse/ServiceClass
- AuthorizedProjectUpdate::ProjectRecalculatePerUserService.new(project, user).execute
+ if blocking
+ AuthorizedProjectUpdate::ProjectRecalculatePerUserService.new(project, user).execute
+ else
+ AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker.perform_async(project.id, user.id)
+ end
# Until we compare the inconsistency rates of the new, specialized service and
# the old approach, we still run AuthorizedProjectsWorker
# but with some delay and lower urgency as a safety net.
UserProjectAccessChangedService.new(user_id)
- .execute(blocking: false, priority: UserProjectAccessChangedService::LOW_PRIORITY)
+ .execute(blocking: false, priority: UserProjectAccessChangedService::LOW_PRIORITY)
# rubocop:enable CodeReuse/ServiceClass
end
- private
-
def send_invite
run_after_commit_or_now { notification_service.invite_project_member(self, @raw_invite_token) }
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index db49ec6f412..15862fb2bfa 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1111,15 +1111,23 @@ class MergeRequest < ApplicationRecord
can_be_merged? && !should_be_rebased?
end
+ # rubocop: disable CodeReuse/ServiceClass
def mergeable_state?(skip_ci_check: false, skip_discussions_check: false)
return false unless open?
return false if work_in_progress?
return false if broken?
- return false unless skip_ci_check || mergeable_ci_state?
return false unless skip_discussions_check || mergeable_discussions_state?
- true
+ if Feature.enabled?(:improved_mergeability_checks, self.project, default_enabled: :yaml)
+ additional_checks = MergeRequests::Mergeability::RunChecksService.new(merge_request: self, params: { skip_ci_check: skip_ci_check })
+ additional_checks.execute.all?(&:success?)
+ else
+ return false unless skip_ci_check || mergeable_ci_state?
+
+ true
+ end
end
+ # rubocop: enable CodeReuse/ServiceClass
def ff_merge_possible?
project.repository.ancestor?(target_branch_sha, diff_head_sha)
@@ -1658,6 +1666,10 @@ class MergeRequest < ApplicationRecord
service_class.new(project, current_user, id: id, report_type: report_type).execute(comparison_base_pipeline(identifier), actual_head_pipeline)
end
+ def recent_diff_head_shas(limit = 100)
+ merge_request_diffs.recent(limit).pluck(:head_commit_sha)
+ end
+
def all_commits
MergeRequestDiffCommit
.where(merge_request_diff: merge_request_diffs.recent)
@@ -1857,7 +1869,7 @@ class MergeRequest < ApplicationRecord
override :ensure_metrics
def ensure_metrics
- if Feature.enabled?(:use_upsert_query_for_mr_metrics)
+ if Feature.enabled?(:use_upsert_query_for_mr_metrics, default_enabled: :yaml)
MergeRequest::Metrics.record!(self)
else
# Backward compatibility: some merge request metrics records will not have target_project_id filled in.
@@ -1918,20 +1930,6 @@ class MergeRequest < ApplicationRecord
end
end
- def lazy_upvotes_count
- BatchLoader.for(id).batch(default_value: 0) do |ids, loader|
- counts = AwardEmoji
- .where(awardable_id: ids)
- .upvotes
- .group(:awardable_id)
- .count
-
- counts.each do |id, count|
- loader.call(id, count)
- end
- end
- end
-
private
def set_draft_status
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index d2b3ca753b1..bd94c0ad30e 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -66,7 +66,7 @@ class MergeRequestDiff < ApplicationRecord
joins(:merge_request).where(merge_requests: { target_project_id: project_id })
end
- scope :recent, -> { order(id: :desc).limit(100) }
+ scope :recent, -> (limit = 100) { order(id: :desc).limit(limit) }
scope :files_in_database, -> do
where(stored_externally: [false, nil]).where(arel_table[:files_count].gt(0))
diff --git a/app/models/metrics/dashboard/annotation.rb b/app/models/metrics/dashboard/annotation.rb
index 3383dda20c9..d3d3f973398 100644
--- a/app/models/metrics/dashboard/annotation.rb
+++ b/app/models/metrics/dashboard/annotation.rb
@@ -32,19 +32,19 @@ module Metrics
def ending_at_after_starting_at
return if ending_at.blank? || starting_at.blank? || starting_at <= ending_at
- errors.add(:ending_at, s_("Metrics::Dashboard::Annotation|can't be before starting_at time"))
+ errors.add(:ending_at, s_("MetricsDashboardAnnotation|can't be before starting_at time"))
end
def single_ownership
return if cluster.nil? ^ environment.nil?
- errors.add(:base, s_("Metrics::Dashboard::Annotation|Annotation can't belong to both a cluster and an environment at the same time"))
+ errors.add(:base, s_("MetricsDashboardAnnotation|Annotation can't belong to both a cluster and an environment at the same time"))
end
def orphaned_annotation
return if cluster.present? || environment.present?
- errors.add(:base, s_("Metrics::Dashboard::Annotation|Annotation must belong to a cluster or an environment"))
+ errors.add(:base, s_("MetricsDashboardAnnotation|Annotation must belong to a cluster or an environment"))
end
end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 0c160cedb4d..e6406293c66 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -28,7 +28,10 @@ class Namespace < ApplicationRecord
# Android repo (15) + some extra backup.
NUMBER_OF_ANCESTORS_ALLOWED = 20
- SHARED_RUNNERS_SETTINGS = %w[disabled_and_unoverridable disabled_with_override enabled].freeze
+ SR_DISABLED_AND_UNOVERRIDABLE = 'disabled_and_unoverridable'
+ SR_DISABLED_WITH_OVERRIDE = 'disabled_with_override'
+ SR_ENABLED = 'enabled'
+ SHARED_RUNNERS_SETTINGS = [SR_DISABLED_AND_UNOVERRIDABLE, SR_DISABLED_WITH_OVERRIDE, SR_ENABLED].freeze
URL_MAX_LENGTH = 255
cache_markdown_field :description, pipeline: :description
@@ -44,6 +47,8 @@ class Namespace < ApplicationRecord
# This should _not_ be `inverse_of: :namespace`, because that would also set
# `user.namespace` when this user creates a group with themselves as `owner`.
+ # TODO: can this be moved into the UserNamespace class?
+ # evaluate in issue https://gitlab.com/gitlab-org/gitlab/-/issues/341070
belongs_to :owner, class_name: "User"
belongs_to :parent, class_name: "Namespace"
@@ -63,21 +68,31 @@ class Namespace < ApplicationRecord
length: { maximum: 255 }
validates :description, length: { maximum: 255 }
+
validates :path,
presence: true,
- length: { maximum: URL_MAX_LENGTH },
- namespace_path: true
+ length: { maximum: URL_MAX_LENGTH }
+
+ validates :path, namespace_path: true, if: ->(n) { !n.project_namespace? }
+ # Project path validator is used for project namespaces for now to assure
+ # compatibility with project paths
+ # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/341764
+ validates :path, project_path: true, if: ->(n) { n.project_namespace? }
# Introduce minimal path length of 2 characters.
# Allow change of other attributes without forcing users to
# rename their user or group. At the same time prevent changing
# the path without complying with new 2 chars requirement.
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/225214
- validates :path, length: { minimum: 2 }, if: :path_changed?
+ #
+ # For ProjectNamespace we don't check minimal path length to keep
+ # compatibility with existing project restrictions.
+ # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/341764
+ validates :path, length: { minimum: 2 }, if: :enforce_minimum_path_length?
validates :max_artifacts_size, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
- validate :validate_parent_type, if: -> { Feature.enabled?(:validate_namespace_parent_type) }
+ validate :validate_parent_type, if: -> { Feature.enabled?(:validate_namespace_parent_type, default_enabled: :yaml) }
validate :nesting_level_allowed
validate :changing_shared_runners_enabled_is_allowed
validate :changing_allow_descendants_override_disabled_shared_runners_is_allowed
@@ -93,7 +108,7 @@ class Namespace < ApplicationRecord
# Legacy Storage specific hooks
- after_update :move_dir, if: :saved_change_to_path_or_parent?
+ after_update :move_dir, if: :saved_change_to_path_or_parent?, unless: -> { is_a?(Namespaces::ProjectNamespace) }
before_destroy(prepend: true) { prepare_for_destroy }
after_destroy :rm_dir
after_commit :expire_child_caches, on: :update, if: -> {
@@ -101,7 +116,12 @@ class Namespace < ApplicationRecord
saved_change_to_name? || saved_change_to_path? || saved_change_to_parent_id?
}
- scope :for_user, -> { where(type: nil) }
+ # TODO: change to `type: Namespaces::UserNamespace.sti_name` when
+ # working on issue https://gitlab.com/gitlab-org/gitlab/-/issues/341070
+ scope :user_namespaces, -> { where(type: [nil, Namespaces::UserNamespace.sti_name]) }
+ # TODO: this can be simplified with `type != 'Project'` when working on issue
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/341070
+ scope :without_project_namespaces, -> { where("type IS DISTINCT FROM ?", Namespaces::ProjectNamespace.sti_name) }
scope :sort_by_type, -> { order(Gitlab::Database.nulls_first_order(:type)) }
scope :include_route, -> { includes(:route) }
scope :by_parent, -> (parent) { where(parent_id: parent) }
@@ -138,14 +158,12 @@ class Namespace < ApplicationRecord
class << self
def sti_class_for(type_name)
case type_name
- when 'Group'
+ when Group.sti_name
Group
- when 'Project'
+ when Namespaces::ProjectNamespace.sti_name
Namespaces::ProjectNamespace
- when 'User'
- # TODO: We create a normal Namespace until
- # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68894 is ready
- Namespace
+ when Namespaces::UserNamespace.sti_name
+ Namespaces::UserNamespace
else
Namespace
end
@@ -247,27 +265,27 @@ class Namespace < ApplicationRecord
end
def kind
- return 'group' if group?
- return 'project' if project?
+ return 'group' if group_namespace?
+ return 'project' if project_namespace?
'user' # defaults to user
end
- def group?
+ def group_namespace?
type == Group.sti_name
end
- def project?
+ def project_namespace?
type == Namespaces::ProjectNamespace.sti_name
end
- def user?
+ def user_namespace?
# That last bit ensures we're considered a user namespace as a default
- type.nil? || type == Namespaces::UserNamespace.sti_name || !(group? || project?)
+ type.nil? || type == Namespaces::UserNamespace.sti_name || !(group_namespace? || project_namespace?)
end
def owner_required?
- user?
+ user_namespace?
end
def find_fork_of(project)
@@ -314,7 +332,7 @@ class Namespace < ApplicationRecord
# that belongs to this namespace
def all_projects
if Feature.enabled?(:recursive_approach_for_all_projects, default_enabled: :yaml)
- namespace = user? ? self : self_and_descendant_ids
+ namespace = user_namespace? ? self : self_and_descendant_ids
Project.where(namespace: namespace)
else
Project.inside_path(full_path)
@@ -416,7 +434,7 @@ class Namespace < ApplicationRecord
def changing_shared_runners_enabled_is_allowed
return unless new_record? || changes.has_key?(:shared_runners_enabled)
- if shared_runners_enabled && has_parent? && parent.shared_runners_setting == 'disabled_and_unoverridable'
+ if shared_runners_enabled && has_parent? && parent.shared_runners_setting == SR_DISABLED_AND_UNOVERRIDABLE
errors.add(:shared_runners_enabled, _('cannot be enabled because parent group has shared Runners disabled'))
end
end
@@ -428,30 +446,30 @@ class Namespace < ApplicationRecord
errors.add(:allow_descendants_override_disabled_shared_runners, _('cannot be changed if shared runners are enabled'))
end
- if allow_descendants_override_disabled_shared_runners && has_parent? && parent.shared_runners_setting == 'disabled_and_unoverridable'
+ if allow_descendants_override_disabled_shared_runners && has_parent? && parent.shared_runners_setting == SR_DISABLED_AND_UNOVERRIDABLE
errors.add(:allow_descendants_override_disabled_shared_runners, _('cannot be enabled because parent group does not allow it'))
end
end
def shared_runners_setting
if shared_runners_enabled
- 'enabled'
+ SR_ENABLED
else
if allow_descendants_override_disabled_shared_runners
- 'disabled_with_override'
+ SR_DISABLED_WITH_OVERRIDE
else
- 'disabled_and_unoverridable'
+ SR_DISABLED_AND_UNOVERRIDABLE
end
end
end
def shared_runners_setting_higher_than?(other_setting)
- if other_setting == 'enabled'
+ if other_setting == SR_ENABLED
false
- elsif other_setting == 'disabled_with_override'
- shared_runners_setting == 'enabled'
- elsif other_setting == 'disabled_and_unoverridable'
- shared_runners_setting == 'enabled' || shared_runners_setting == 'disabled_with_override'
+ elsif other_setting == SR_DISABLED_WITH_OVERRIDE
+ shared_runners_setting == SR_ENABLED
+ elsif other_setting == SR_DISABLED_AND_UNOVERRIDABLE
+ shared_runners_setting == SR_ENABLED || shared_runners_setting == SR_DISABLED_WITH_OVERRIDE
else
raise ArgumentError
end
@@ -536,21 +554,21 @@ class Namespace < ApplicationRecord
def validate_parent_type
unless has_parent?
- if project?
+ if project_namespace?
errors.add(:parent_id, _('must be set for a project namespace'))
end
return
end
- if parent.project?
+ if parent.project_namespace?
errors.add(:parent_id, _('project namespace cannot be the parent of another namespace'))
end
- if user?
+ if user_namespace?
errors.add(:parent_id, _('cannot not be used for user namespace'))
- elsif group?
- errors.add(:parent_id, _('user namespace cannot be the parent of another namespace')) if parent.user?
+ elsif group_namespace?
+ errors.add(:parent_id, _('user namespace cannot be the parent of another namespace')) if parent.user_namespace?
end
end
@@ -575,6 +593,10 @@ class Namespace < ApplicationRecord
project.track_project_repository
end
end
+
+ def enforce_minimum_path_length?
+ path_changed? && !project_namespace?
+ end
end
Namespace.prepend_mod_with('Namespace')
diff --git a/app/models/namespace/root_storage_statistics.rb b/app/models/namespace/root_storage_statistics.rb
index 73061b78637..99e32537595 100644
--- a/app/models/namespace/root_storage_statistics.rb
+++ b/app/models/namespace/root_storage_statistics.rb
@@ -57,7 +57,7 @@ class Namespace::RootStorageStatistics < ApplicationRecord
end
def attributes_from_personal_snippets
- return {} unless namespace.user?
+ return {} unless namespace.user_namespace?
from_personal_snippets.take.slice(SNIPPETS_SIZE_STAT_NAME)
end
diff --git a/app/models/namespaces/user_namespace.rb b/app/models/namespaces/user_namespace.rb
index 517d68b118d..22b7a0a3b2b 100644
--- a/app/models/namespaces/user_namespace.rb
+++ b/app/models/namespaces/user_namespace.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# TODO: currently not created/mapped in the database, will be done in another issue
-# https://gitlab.com/gitlab-org/gitlab/-/issues/337102
+# https://gitlab.com/gitlab-org/gitlab/-/issues/341070
module Namespaces
class UserNamespace < Namespace
def self.sti_name
diff --git a/app/models/note.rb b/app/models/note.rb
index a8f5c305d9b..37473518892 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -149,7 +149,7 @@ class Note < ApplicationRecord
scope :like_note_or_capitalized_note, ->(text) { where('(note LIKE ? OR note LIKE ?)', text, text.capitalize) }
before_validation :nullify_blank_type, :nullify_blank_line_code
- after_save :keep_around_commit, if: :for_project_noteable?, unless: :importing?
+ after_save :keep_around_commit, if: :for_project_noteable?, unless: -> { importing? || skip_keep_around_commits }
after_save :expire_etag_cache, unless: :importing?
after_save :touch_noteable, unless: :importing?
after_destroy :expire_etag_cache
@@ -355,8 +355,6 @@ class Note < ApplicationRecord
end
def noteable_author?(noteable)
- return false unless ::Feature.enabled?(:show_author_on_note, project)
-
noteable.author == self.author
end
diff --git a/app/models/operations/feature_flag.rb b/app/models/operations/feature_flag.rb
index 46810749b18..7db396bcad5 100644
--- a/app/models/operations/feature_flag.rb
+++ b/app/models/operations/feature_flag.rb
@@ -19,13 +19,10 @@ module Operations
default_value_for :active, true
default_value_for :version, :new_version_flag
- # scopes exists only for the first version
- has_many :scopes, class_name: 'Operations::FeatureFlagScope'
# strategies exists only for the second version
has_many :strategies, class_name: 'Operations::FeatureFlags::Strategy'
has_many :feature_flag_issues
has_many :issues, through: :feature_flag_issues
- has_one :default_scope, -> { where(environment_scope: '*') }, class_name: 'Operations::FeatureFlagScope'
validates :project, presence: true
validates :name,
@@ -37,10 +34,7 @@ module Operations
}
validates :name, uniqueness: { scope: :project_id }
validates :description, allow_blank: true, length: 0..255
- validate :first_default_scope, on: :create, if: :has_scopes?
- validate :version_associations
- accepts_nested_attributes_for :scopes, allow_destroy: true
accepts_nested_attributes_for :strategies, allow_destroy: true
scope :ordered, -> { order(:name) }
@@ -56,7 +50,7 @@ module Operations
class << self
def preload_relations
- preload(:scopes, strategies: :scopes)
+ preload(strategies: :scopes)
end
def for_unleash_client(project, environment)
@@ -104,13 +98,6 @@ module Operations
Ability.issues_readable_by_user(issues, current_user)
end
- def execute_hooks(current_user)
- run_after_commit do
- feature_flag_data = Gitlab::DataBuilder::FeatureFlag.build(self, current_user)
- project.execute_hooks(feature_flag_data, :feature_flag_hooks)
- end
- end
-
def hook_attrs
{
id: id,
@@ -119,27 +106,5 @@ module Operations
active: active
}
end
-
- private
-
- def version_associations
- if new_version_flag? && scopes.any?
- errors.add(:version_associations, 'version 2 feature flags may not have scopes')
- end
- end
-
- def first_default_scope
- unless scopes.first.environment_scope == '*'
- errors.add(:default_scope, 'has to be the first element')
- end
- end
-
- def build_default_scope
- scopes.build(environment_scope: '*', active: self.active)
- end
-
- def has_scopes?
- scopes.any?
- end
end
end
diff --git a/app/models/operations/feature_flag_scope.rb b/app/models/operations/feature_flag_scope.rb
deleted file mode 100644
index 9068ca0f588..00000000000
--- a/app/models/operations/feature_flag_scope.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-# All of the legacy flags have been removed in 14.1, including all of the
-# `operations_feature_flag_scopes` rows. Therefore, this model and the database
-# table are unused and should be removed.
-
-module Operations
- class FeatureFlagScope < ApplicationRecord
- prepend HasEnvironmentScope
- include Gitlab::Utils::StrongMemoize
-
- self.table_name = 'operations_feature_flag_scopes'
-
- belongs_to :feature_flag
-
- validates :environment_scope, uniqueness: {
- scope: :feature_flag,
- message: "(%{value}) has already been taken"
- }
-
- validates :environment_scope,
- if: :default_scope?, on: :update,
- inclusion: { in: %w(*), message: 'cannot be changed from default scope' }
-
- validates :strategies, feature_flag_strategies: true
-
- before_destroy :prevent_destroy_default_scope, if: :default_scope?
-
- scope :ordered, -> { order(:id) }
- scope :enabled, -> { where(active: true) }
- scope :disabled, -> { where(active: false) }
-
- def self.with_name_and_description
- joins(:feature_flag)
- .select(FeatureFlag.arel_table[:name], FeatureFlag.arel_table[:description])
- end
-
- def self.for_unleash_client(project, environment)
- select_columns = [
- 'DISTINCT ON (operations_feature_flag_scopes.feature_flag_id) operations_feature_flag_scopes.id',
- '(operations_feature_flags.active AND operations_feature_flag_scopes.active) AS active',
- 'operations_feature_flag_scopes.strategies',
- 'operations_feature_flag_scopes.environment_scope',
- 'operations_feature_flag_scopes.created_at',
- 'operations_feature_flag_scopes.updated_at'
- ]
-
- select(select_columns)
- .with_name_and_description
- .where(feature_flag_id: project.operations_feature_flags.select(:id))
- .order(:feature_flag_id)
- .on_environment(environment)
- .reverse_order
- end
-
- private
-
- def default_scope?
- environment_scope_was == '*'
- end
-
- def prevent_destroy_default_scope
- raise ActiveRecord::ReadOnlyRecord, "default scope cannot be destroyed"
- end
- end
-end
diff --git a/app/models/packages/composer/cache_file.rb b/app/models/packages/composer/cache_file.rb
index ecd7596b989..5222101d171 100644
--- a/app/models/packages/composer/cache_file.rb
+++ b/app/models/packages/composer/cache_file.rb
@@ -9,15 +9,13 @@ module Packages
mount_file_store_uploader Packages::Composer::CacheUploader
- belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
+ belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'namespace_id'
belongs_to :namespace
validates :namespace, presence: true
scope :with_namespace, ->(namespace) { where(namespace: namespace) }
scope :with_sha, ->(sha) { where(file_sha256: sha) }
- scope :expired, -> { where("delete_at <= ?", Time.current) }
- scope :without_namespace, -> { where(namespace_id: nil) }
end
end
end
diff --git a/app/models/packages/helm/file_metadatum.rb b/app/models/packages/helm/file_metadatum.rb
index 1771003d1f9..dfa4ab6df82 100644
--- a/app/models/packages/helm/file_metadatum.rb
+++ b/app/models/packages/helm/file_metadatum.rb
@@ -12,7 +12,7 @@ module Packages
validates :channel,
presence: true,
- length: { maximum: 63 },
+ length: { maximum: 255 },
format: { with: Gitlab::Regex.helm_channel_regex }
validates :metadata,
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index c932d0bf800..0c5a155d48a 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -129,18 +129,15 @@ class PagesDomain < ApplicationRecord
store = OpenSSL::X509::Store.new
store.set_default_paths
- # This forces to load all intermediate certificates stored in `certificate`
- Tempfile.open('certificate_chain') do |f|
- f.write(certificate)
- f.flush
- store.add_file(f.path)
- end
-
- store.verify(x509)
+ store.verify(x509, untrusted_ca_certs_bundle)
rescue OpenSSL::X509::StoreError
false
end
+ def untrusted_ca_certs_bundle
+ ::Gitlab::X509::Certificate.load_ca_certs_bundle(certificate)
+ end
+
def expired?
return false unless x509
diff --git a/app/models/preloaders/merge_requests_preloader.rb b/app/models/preloaders/merge_requests_preloader.rb
deleted file mode 100644
index cefe8408cab..00000000000
--- a/app/models/preloaders/merge_requests_preloader.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-module Preloaders
- class MergeRequestsPreloader
- attr_reader :merge_requests
-
- def initialize(merge_requests)
- @merge_requests = merge_requests
- end
-
- def execute
- preloader = ActiveRecord::Associations::Preloader.new
- preloader.preload(merge_requests, { target_project: [:project_feature] })
- merge_requests.each do |merge_request|
- merge_request.lazy_upvotes_count
- end
- end
- end
-end
diff --git a/app/models/product_analytics_event.rb b/app/models/product_analytics_event.rb
index d2026d3b333..52baa3be6c4 100644
--- a/app/models/product_analytics_event.rb
+++ b/app/models/product_analytics_event.rb
@@ -20,8 +20,6 @@ class ProductAnalyticsEvent < ApplicationRecord
where('collector_tstamp BETWEEN ? AND ? ', today - duration + 1, today + 1)
}
- scope :by_category_and_action, ->(category, action) { where(se_category: category, se_action: action) }
-
def self.count_by_graph(graph, days)
group(graph).timerange(days).count
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 74ffeef797e..6eb19b4462c 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -98,6 +98,7 @@ class Project < ApplicationRecord
before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? }
before_save :ensure_runners_token
+ before_save :ensure_project_namespace_in_sync
after_save :update_project_statistics, if: :saved_change_to_namespace_id?
@@ -128,26 +129,9 @@ class Project < ApplicationRecord
after_initialize :use_hashed_storage
after_create :check_repository_absence!
- # Required during the `ActsAsTaggableOn::Tag -> Topic` migration
- # TODO: remove 'acts_as_ordered_taggable_on' and ':topics_acts_as_taggable' in the further process of the migration
- # https://gitlab.com/gitlab-org/gitlab/-/issues/335946
- acts_as_ordered_taggable_on :topics
- has_many :topics_acts_as_taggable, -> { order("#{ActsAsTaggableOn::Tagging.table_name}.id") },
- class_name: 'ActsAsTaggableOn::Tag',
- through: :topic_taggings,
- source: :tag
-
has_many :project_topics, -> { order(:id) }, class_name: 'Projects::ProjectTopic'
has_many :topics, through: :project_topics, class_name: 'Projects::Topic'
- # Required during the `ActsAsTaggableOn::Tag -> Topic` migration
- # TODO: remove 'topics' in the further process of the migration
- # https://gitlab.com/gitlab-org/gitlab/-/issues/335946
- alias_method :topics_new, :topics
- def topics
- self.topics_acts_as_taggable + self.topics_new
- end
-
attr_accessor :old_path_with_namespace
attr_accessor :template_name
attr_writer :pipeline_status
@@ -159,11 +143,11 @@ class Project < ApplicationRecord
# Relations
belongs_to :pool_repository
belongs_to :creator, class_name: 'User'
- belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
+ belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'namespace_id'
belongs_to :namespace
# Sync deletion via DB Trigger to ensure we do not have
# a project without a project_namespace (or vice-versa)
- belongs_to :project_namespace, class_name: 'Namespaces::ProjectNamespace', foreign_key: 'project_namespace_id', inverse_of: :project
+ belongs_to :project_namespace, autosave: true, class_name: 'Namespaces::ProjectNamespace', foreign_key: 'project_namespace_id', inverse_of: :project
alias_method :parent, :namespace
alias_attribute :parent_id, :namespace_id
@@ -233,6 +217,7 @@ class Project < ApplicationRecord
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :export_jobs, class_name: 'ProjectExportJob'
+ has_many :bulk_import_exports, class_name: 'BulkImports::Export', inverse_of: :project
has_one :project_repository, inverse_of: :project
has_one :tracing_setting, class_name: 'ProjectTracingSetting'
has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting'
@@ -652,15 +637,8 @@ class Project < ApplicationRecord
scope :with_topic, ->(topic_name) do
topic = Projects::Topic.find_by_name(topic_name)
- acts_as_taggable_on_topic = ActsAsTaggableOn::Tag.find_by_name(topic_name)
-
- return none unless topic || acts_as_taggable_on_topic
-
- relations = []
- relations << where(id: topic.project_topics.select(:project_id)) if topic
- relations << where(id: acts_as_taggable_on_topic.taggings.select(:taggable_id)) if acts_as_taggable_on_topic
- Project.from_union(relations)
+ topic ? where(id: topic.project_topics.select(:project_id)) : none
end
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
@@ -678,7 +656,7 @@ class Project < ApplicationRecord
mount_uploader :bfg_object_map, AttachmentUploader
def self.with_api_entity_associations
- preload(:project_feature, :route, :topics, :topics_acts_as_taggable, :group, :timelogs, namespace: [:route, :owner])
+ preload(:project_feature, :route, :topics, :group, :timelogs, namespace: [:route, :owner])
end
def self.with_web_entity_associations
@@ -851,7 +829,7 @@ class Project < ApplicationRecord
end
def group_ids
- joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
+ joins(:namespace).where(namespaces: { type: Group.sti_name }).select(:namespace_id)
end
# Returns ids of projects with issuables available for given user
@@ -1200,7 +1178,7 @@ class Project < ApplicationRecord
end
def import?
- external_import? || forked? || gitlab_project_import? || jira_import? || bare_repository_import?
+ external_import? || forked? || gitlab_project_import? || jira_import? || bare_repository_import? || gitlab_project_migration?
end
def external_import?
@@ -1223,6 +1201,10 @@ class Project < ApplicationRecord
import_type == 'gitlab_project'
end
+ def gitlab_project_migration?
+ import_type == 'gitlab_project_migration'
+ end
+
def gitea_import?
import_type == 'gitea'
end
@@ -1327,11 +1309,21 @@ class Project < ApplicationRecord
def changing_shared_runners_enabled_is_allowed
return unless new_record? || changes.has_key?(:shared_runners_enabled)
- if shared_runners_enabled && group && group.shared_runners_setting == 'disabled_and_unoverridable'
+ if shared_runners_setting_conflicting_with_group?
errors.add(:shared_runners_enabled, _('cannot be enabled because parent group does not allow it'))
end
end
+ def shared_runners_setting_conflicting_with_group?
+ shared_runners_enabled && group&.shared_runners_setting == Namespace::SR_DISABLED_AND_UNOVERRIDABLE
+ end
+
+ def reconcile_shared_runners_setting!
+ if shared_runners_setting_conflicting_with_group?
+ self.shared_runners_enabled = false
+ end
+ end
+
def to_param
if persisted? && errors.include?(:path)
path_was
@@ -1814,7 +1806,7 @@ class Project < ApplicationRecord
def open_issues_count(current_user = nil)
return Projects::OpenIssuesCountService.new(self, current_user).count unless current_user.nil?
- BatchLoader.for(self).batch(replace_methods: false) do |projects, loader|
+ BatchLoader.for(self).batch do |projects, loader|
issues_count_per_project = ::Projects::BatchOpenIssuesCountService.new(projects).refresh_cache_and_retrieve_data
issues_count_per_project.each do |project, count|
@@ -2279,7 +2271,7 @@ class Project < ApplicationRecord
# rubocop: disable CodeReuse/ServiceClass
def forks_count
- BatchLoader.for(self).batch(replace_methods: false) do |projects, loader|
+ BatchLoader.for(self).batch do |projects, loader|
fork_count_per_project = ::Projects::BatchForksCountService.new(projects).refresh_cache_and_retrieve_data
fork_count_per_project.each do |project, count|
@@ -2418,7 +2410,7 @@ class Project < ApplicationRecord
end
def mark_primary_write_location
- ::Gitlab::Database::LoadBalancing::Sticking.mark_primary_write_location(:project, self.id)
+ self.class.sticking.mark_primary_write_location(:project, self.id)
end
def toggle_ci_cd_settings!(settings_attribute)
@@ -2677,10 +2669,6 @@ class Project < ApplicationRecord
ProjectStatistics.increment_statistic(self, statistic, delta)
end
- def merge_requests_author_approval
- !!read_attribute(:merge_requests_author_approval)
- end
-
def ci_forward_deployment_enabled?
return false unless ci_cd_settings
@@ -2734,15 +2722,9 @@ class Project < ApplicationRecord
@topic_list = @topic_list.split(',') if @topic_list.instance_of?(String)
@topic_list = @topic_list.map(&:strip).uniq.reject(&:empty?)
- if @topic_list != self.topic_list || self.topics_acts_as_taggable.any?
- self.topics_new.delete_all
+ if @topic_list != self.topic_list
+ self.topics.delete_all
self.topics = @topic_list.map { |topic| Projects::Topic.find_or_create_by(name: topic) }
-
- # Remove old topics (ActsAsTaggableOn::Tag)
- # Required during the `ActsAsTaggableOn::Tag -> Topic` migration
- # TODO: remove in the further process of the migration
- # https://gitlab.com/gitlab-org/gitlab/-/issues/335946
- self.topic_taggings.clear
end
@topic_list = nil
@@ -2912,6 +2894,15 @@ class Project < ApplicationRecord
def online_runners_with_tags
@online_runners_with_tags ||= active_runners.with_tags.online
end
+
+ def ensure_project_namespace_in_sync
+ if changes.keys & [:name, :path, :namespace_id, :visibility_level] && project_namespace.present?
+ project_namespace.name = name
+ project_namespace.path = path
+ project_namespace.parent = namespace
+ project_namespace.visibility_level = visibility_level
+ end
+ end
end
Project.prepend_mod_with('Project')
diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb
index b2559636f32..24d892290a6 100644
--- a/app/models/project_setting.rb
+++ b/app/models/project_setting.rb
@@ -1,10 +1,6 @@
# frozen_string_literal: true
class ProjectSetting < ApplicationRecord
- include IgnorableColumns
-
- ignore_column :allow_editing_commit_messages, remove_with: '14.4', remove_after: '2021-09-10'
-
belongs_to :project, inverse_of: :project_setting
enum squash_option: {
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 387732cf151..99cec647a98 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -31,7 +31,6 @@ class ProjectStatistics < ApplicationRecord
scope :for_project_ids, ->(project_ids) { where(project_id: project_ids) }
scope :for_namespaces, -> (namespaces) { where(namespace: namespaces) }
- scope :with_any_ci_minutes_used, -> { where.not(shared_runners_seconds: 0) }
def total_repository_size
repository_size + lfs_objects_size
@@ -70,7 +69,7 @@ class ProjectStatistics < ApplicationRecord
end
def update_lfs_objects_size
- self.lfs_objects_size = project.lfs_objects.sum(:size)
+ self.lfs_objects_size = LfsObject.joins(:lfs_objects_projects).where(lfs_objects_projects: { project_id: project.id }).sum(:size)
end
def update_uploads_size
diff --git a/app/models/projects/project_topic.rb b/app/models/projects/project_topic.rb
index d4b456ef482..7021a48646a 100644
--- a/app/models/projects/project_topic.rb
+++ b/app/models/projects/project_topic.rb
@@ -3,6 +3,6 @@
module Projects
class ProjectTopic < ApplicationRecord
belongs_to :project
- belongs_to :topic
+ belongs_to :topic, counter_cache: :total_projects_count
end
end
diff --git a/app/models/projects/topic.rb b/app/models/projects/topic.rb
index a17aa550edb..f3352ecc5ee 100644
--- a/app/models/projects/topic.rb
+++ b/app/models/projects/topic.rb
@@ -1,10 +1,30 @@
# frozen_string_literal: true
+require 'carrierwave/orm/activerecord'
+
module Projects
class Topic < ApplicationRecord
+ include Avatarable
+ include Gitlab::SQL::Pattern
+
validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
+ validates :description, length: { maximum: 1024 }
has_many :project_topics, class_name: 'Projects::ProjectTopic'
has_many :projects, through: :project_topics
+
+ scope :order_by_total_projects_count, -> { order(total_projects_count: :desc).order(id: :asc) }
+ scope :reorder_by_similarity, -> (search) do
+ order_expression = Gitlab::Database::SimilarityScore.build_expression(search: search, rules: [
+ { column: arel_table['name'] }
+ ])
+ reorder(order_expression.desc, arel_table['total_projects_count'].desc, arel_table['id'])
+ end
+
+ class << self
+ def search(query)
+ fuzzy_search(query, [:name])
+ end
+ end
end
end
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 3d32144e0f8..b4e2d17c3e5 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -10,6 +10,8 @@ class ProtectedBranch < ApplicationRecord
scope :allowing_force_push,
-> { where(allow_force_push: true) }
+ scope :get_ids_by_name, -> (name) { where(name: name).pluck(:id) }
+
protected_ref_access_levels :merge, :push
def self.protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil)
diff --git a/app/models/release.rb b/app/models/release.rb
index 0dd71c6ebfb..eac6346cc60 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -33,6 +33,7 @@ class Release < ApplicationRecord
includes(:author, :evidences, :milestones, :links, :sorted_links,
project: [:project_feature, :route, { namespace: :route }])
}
+ scope :with_milestones, -> { joins(:milestone_releases) }
scope :recent, -> { sorted.limit(MAX_NUMBER_TO_DISPLAY) }
scope :without_evidence, -> { left_joins(:evidences).where(::Releases::Evidence.arel_table[:id].eq(nil)) }
scope :released_within_2hrs, -> { where(released_at: Time.zone.now - 1.hour..Time.zone.now + 1.hour) }
diff --git a/app/models/repository.rb b/app/models/repository.rb
index f20b306c806..119d874a6e1 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -732,7 +732,7 @@ class Repository
end
def tags_sorted_by(value)
- return raw_repository.tags(sort_by: value) if Feature.enabled?(:gitaly_tags_finder, project, default_enabled: :yaml)
+ return raw_repository.tags(sort_by: value) if Feature.enabled?(:tags_finder_gitaly, project, default_enabled: :yaml)
tags_ruby_sort(value)
end
@@ -1054,10 +1054,10 @@ class Repository
end
def squash(user, merge_request, message)
- raw.squash(user, merge_request.id, start_sha: merge_request.diff_start_sha,
- end_sha: merge_request.diff_head_sha,
- author: merge_request.author,
- message: message)
+ raw.squash(user, start_sha: merge_request.diff_start_sha,
+ end_sha: merge_request.diff_head_sha,
+ author: merge_request.author,
+ message: message)
end
def submodule_links
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 0a4acdfc7e3..c1a3df82457 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -18,6 +18,8 @@ class Upload < ApplicationRecord
before_save :calculate_checksum!, if: :foreground_checksummable?
after_commit :schedule_checksum, if: :needs_checksum?
+ after_commit :update_project_statistics, on: [:create, :destroy], if: :project?
+
# as the FileUploader is not mounted, the default CarrierWave ActiveRecord
# hooks are not executed and the file will not be deleted
after_destroy :delete_file!, if: -> { uploader_class <= FileUploader }
@@ -67,7 +69,7 @@ class Upload < ApplicationRecord
self.checksum = nil
return unless needs_checksum?
- self.checksum = self.class.hexdigest(absolute_path)
+ self.checksum = self.class.sha256_hexdigest(absolute_path)
end
# Initialize the associated Uploader class with current model
@@ -161,6 +163,14 @@ class Upload < ApplicationRecord
def mount_point
super&.to_sym
end
+
+ def project?
+ model_type == "Project"
+ end
+
+ def update_project_statistics
+ ProjectCacheWorker.perform_async(model_id, [], [:uploads_size])
+ end
end
Upload.prepend_mod_with('Upload')
diff --git a/app/models/user.rb b/app/models/user.rb
index a4c8d606911..25a2588a6a7 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -112,7 +112,14 @@ class User < ApplicationRecord
#
# Namespace for personal projects
- has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, inverse_of: :owner, autosave: true # rubocop:disable Cop/ActiveRecordDependent
+ # TODO: change to `:namespace, -> { where(type: Namespaces::UserNamespace.sti_name}, class_name: 'Namespaces::UserNamespace'...`
+ # when working on issue https://gitlab.com/gitlab-org/gitlab/-/issues/341070
+ has_one :namespace,
+ -> { where(type: [nil, Namespaces::UserNamespace.sti_name]) },
+ dependent: :destroy, # rubocop:disable Cop/ActiveRecordDependent
+ foreign_key: :owner_id,
+ inverse_of: :owner,
+ autosave: true # rubocop:disable Cop/ActiveRecordDependent
# Profile
has_many :keys, -> { regular_keys }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -229,9 +236,9 @@ class User < ApplicationRecord
validates :first_name, length: { maximum: 127 }
validates :last_name, length: { maximum: 127 }
validates :email, confirmation: true
- validates :notification_email, devise_email: true, allow_blank: true, if: ->(user) { user.notification_email != user.email }
+ validates :notification_email, devise_email: true, allow_blank: true
validates :public_email, uniqueness: true, devise_email: true, allow_blank: true
- validates :commit_email, devise_email: true, allow_blank: true, if: ->(user) { user.commit_email != user.email && user.commit_email != Gitlab::PrivateCommitEmail::TOKEN }
+ validates :commit_email, devise_email: true, allow_blank: true, unless: ->(user) { user.commit_email == Gitlab::PrivateCommitEmail::TOKEN }
validates :projects_limit,
presence: true,
numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
@@ -316,6 +323,7 @@ class User < ApplicationRecord
delegate :webauthn_xid, :webauthn_xid=, to: :user_detail, allow_nil: true
delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true
delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true
+ delegate :registration_objective, :registration_objective=, to: :user_detail, allow_nil: true
accepts_nested_attributes_for :user_preference, update_only: true
accepts_nested_attributes_for :user_detail, update_only: true
@@ -449,11 +457,12 @@ class User < ApplicationRecord
scope :dormant, -> { active.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) }
scope :with_no_activity, -> { active.where(last_activity_on: nil) }
scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) }
+ scope :get_ids_by_username, -> (username) { where(username: username).pluck(:id) }
def preferred_language
read_attribute('preferred_language') ||
I18n.default_locale.to_s.presence_in(Gitlab::I18n.available_locales) ||
- 'en'
+ default_preferred_language
end
def active_for_authentication?
@@ -728,7 +737,7 @@ class User < ApplicationRecord
end
def find_by_full_path(path, follow_redirects: false)
- namespace = Namespace.for_user.find_by_full_path(path, follow_redirects: follow_redirects)
+ namespace = Namespace.user_namespaces.find_by_full_path(path, follow_redirects: follow_redirects)
namespace&.owner
end
@@ -1434,7 +1443,10 @@ class User < ApplicationRecord
namespace.path = username if username_changed?
namespace.name = name if name_changed?
else
- namespace = build_namespace(path: username, name: name)
+ # TODO: we should no longer need the `type` parameter once we can make the
+ # the `has_one :namespace` association use the correct class.
+ # issue https://gitlab.com/gitlab-org/gitlab/-/issues/341070
+ namespace = build_namespace(path: username, name: name, type: ::Namespaces::UserNamespace.sti_name)
namespace.build_namespace_settings
end
end
@@ -2003,6 +2015,11 @@ class User < ApplicationRecord
private
+ # To enable JiHu repository to modify the default language options
+ def default_preferred_language
+ 'en'
+ end
+
def notification_email_verified
return if notification_email.blank? || temp_oauth_email?
@@ -2094,10 +2111,14 @@ class User < ApplicationRecord
errors.add(:email, error) if error
end
+ def signup_email_invalid_message
+ _('is not allowed for sign-up.')
+ end
+
def check_username_format
return if username.blank? || Mime::EXTENSION_LOOKUP.keys.none? { |type| username.end_with?(".#{type}") }
- errors.add(:username, _('ending with a file extension is not allowed.'))
+ errors.add(:username, _('ending with a reserved file extension is not allowed.'))
end
def groups_with_developer_maintainer_project_access
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
index 04bc29755f8..b990aedd4f8 100644
--- a/app/models/user_callout.rb
+++ b/app/models/user_callout.rb
@@ -36,7 +36,8 @@ class UserCallout < ApplicationRecord
trial_status_reminder_d3: 35, # EE-only
security_configuration_devops_alert: 36, # EE-only
profile_personal_access_token_expiry: 37, # EE-only
- terraform_notification_dismissed: 38
+ terraform_notification_dismissed: 38,
+ security_newsletter_callout: 39
}
validates :feature_name,
diff --git a/app/models/user_detail.rb b/app/models/user_detail.rb
index c41cff67864..6b0ed89c683 100644
--- a/app/models/user_detail.rb
+++ b/app/models/user_detail.rb
@@ -3,7 +3,10 @@
class UserDetail < ApplicationRecord
extend ::Gitlab::Utils::Override
include IgnorableColumns
- ignore_columns %i[bio_html cached_markdown_version], remove_with: '13.6', remove_after: '2021-10-22'
+
+ ignore_columns %i[bio_html cached_markdown_version], remove_with: '14.5', remove_after: '2021-10-22'
+
+ REGISTRATION_OBJECTIVE_PAIRS = { basics: 0, move_repository: 1, code_storage: 2, exploring: 3, ci: 4, other: 5, joining_team: 6 }.freeze
belongs_to :user
@@ -14,6 +17,8 @@ class UserDetail < ApplicationRecord
before_save :prevent_nil_bio
+ enum registration_objective: REGISTRATION_OBJECTIVE_PAIRS, _suffix: true
+
private
def prevent_nil_bio
diff --git a/app/models/user_highest_role.rb b/app/models/user_highest_role.rb
index 4853fc3d248..dd5c85a5a87 100644
--- a/app/models/user_highest_role.rb
+++ b/app/models/user_highest_role.rb
@@ -3,7 +3,13 @@
class UserHighestRole < ApplicationRecord
belongs_to :user, optional: false
- validates :highest_access_level, allow_nil: true, inclusion: { in: Gitlab::Access.all_values }
+ validates :highest_access_level, allow_nil: true, inclusion: { in: ->(_) { self.allowed_values } }
scope :with_highest_access_level, -> (highest_access_level) { where(highest_access_level: highest_access_level) }
+
+ def self.allowed_values
+ Gitlab::Access.all_values
+ end
end
+
+UserHighestRole.prepend_mod
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index 337ae7125f3..7687430cfd1 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -23,7 +23,6 @@ class UserPreference < ApplicationRecord
ignore_columns :experience_level, remove_with: '14.10', remove_after: '2021-03-22'
default_value_for :tab_width, value: Gitlab::TabWidth::DEFAULT, allows_nil: false
- default_value_for :timezone, value: Time.zone.tzinfo.name, allows_nil: false
default_value_for :time_display_relative, value: true, allows_nil: false
default_value_for :time_format_in_24h, value: false, allows_nil: false
default_value_for :render_whitespace_in_code, value: false, allows_nil: false
diff --git a/app/models/users/credit_card_validation.rb b/app/models/users/credit_card_validation.rb
index 5e255acd882..a4cc43d1f13 100644
--- a/app/models/users/credit_card_validation.rb
+++ b/app/models/users/credit_card_validation.rb
@@ -7,5 +7,18 @@ module Users
self.table_name = 'user_credit_card_validations'
belongs_to :user
+
+ validates :holder_name, length: { maximum: 26 }
+ validates :last_digits, allow_nil: true, numericality: {
+ greater_than_or_equal_to: 0, less_than_or_equal_to: 9999
+ }
+
+ def similar_records
+ self.class.where(
+ expiration_date: expiration_date,
+ last_digits: last_digits,
+ holder_name: holder_name
+ ).order(credit_card_validated_at: :desc).includes(:user)
+ end
end
end