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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/services')
-rw-r--r--app/services/achievements/update_user_achievement_priorities_service.rb44
-rw-r--r--app/services/admin/abuse_reports/moderate_user_service.rb5
-rw-r--r--app/services/audit_events/build_service.rb9
-rw-r--r--app/services/auth/container_registry_authentication_service.rb32
-rw-r--r--app/services/auto_merge/base_service.rb21
-rw-r--r--app/services/branches/delete_service.rb2
-rw-r--r--app/services/bulk_create_integration_service.rb4
-rw-r--r--app/services/bulk_imports/process_service.rb129
-rw-r--r--app/services/bulk_imports/relation_batch_export_service.rb11
-rw-r--r--app/services/bulk_imports/relation_export_service.rb11
-rw-r--r--app/services/chat_names/find_user_service.rb7
-rw-r--r--app/services/ci/catalog/resources/validate_service.rb48
-rw-r--r--app/services/ci/catalog/validate_resource_service.rb46
-rw-r--r--app/services/ci/components/fetch_service.rb15
-rw-r--r--app/services/ci/job_artifacts/destroy_all_expired_service.rb4
-rw-r--r--app/services/ci/pipeline_creation/cancel_redundant_pipelines_service.rb24
-rw-r--r--app/services/ci/pipeline_processing/atomic_processing_service.rb2
-rw-r--r--app/services/ci/refs/enqueue_pipelines_to_unlock_service.rb32
-rw-r--r--app/services/ci/retry_job_service.rb4
-rw-r--r--app/services/ci/retry_pipeline_service.rb8
-rw-r--r--app/services/ci/unlock_pipeline_service.rb107
-rw-r--r--app/services/clusters/agent_tokens/revoke_service.rb2
-rw-r--r--app/services/clusters/cleanup/project_namespace_service.rb2
-rw-r--r--app/services/clusters/cleanup/service_account_service.rb2
-rw-r--r--app/services/commits/create_service.rb6
-rw-r--r--app/services/concerns/update_repository_storage_methods.rb8
-rw-r--r--app/services/concerns/users/participable_service.rb2
-rw-r--r--app/services/deployments/create_for_job_service.rb2
-rw-r--r--app/services/deployments/create_service.rb1
-rw-r--r--app/services/git/branch_hooks_service.rb2
-rw-r--r--app/services/import/bitbucket_server_service.rb7
-rw-r--r--app/services/import/github_service.rb1
-rw-r--r--app/services/import/validate_remote_git_endpoint_service.rb81
-rw-r--r--app/services/issuable/clone/base_service.rb2
-rw-r--r--app/services/issuable_links/create_service.rb4
-rw-r--r--app/services/issue_links/create_service.rb2
-rw-r--r--app/services/issues/set_crm_contacts_service.rb4
-rw-r--r--app/services/jira_connect/sync_service.rb8
-rw-r--r--app/services/members/create_service.rb35
-rw-r--r--app/services/members/creator_service.rb21
-rw-r--r--app/services/merge_requests/approval_service.rb13
-rw-r--r--app/services/merge_requests/merge_service.rb2
-rw-r--r--app/services/merge_requests/mergeability/check_base_service.rb18
-rw-r--r--app/services/merge_requests/mergeability/check_broken_status_service.rb10
-rw-r--r--app/services/merge_requests/mergeability/check_ci_status_service.rb10
-rw-r--r--app/services/merge_requests/mergeability/check_conflict_status_service.rb27
-rw-r--r--app/services/merge_requests/mergeability/check_discussions_status_service.rb10
-rw-r--r--app/services/merge_requests/mergeability/check_draft_status_service.rb12
-rw-r--r--app/services/merge_requests/mergeability/check_open_status_service.rb10
-rw-r--r--app/services/merge_requests/mergeability/check_rebase_status_service.rb27
-rw-r--r--app/services/merge_requests/mergeability/detailed_merge_status_service.rb8
-rw-r--r--app/services/merge_requests/mergeability/run_checks_service.rb34
-rw-r--r--app/services/merge_requests/update_service.rb9
-rw-r--r--app/services/ml/find_or_create_model_version_service.rb1
-rw-r--r--app/services/notes/quick_actions_service.rb2
-rw-r--r--app/services/packages/create_dependency_service.rb2
-rw-r--r--app/services/packages/maven/find_or_create_package_service.rb2
-rw-r--r--app/services/packages/npm/create_package_service.rb2
-rw-r--r--app/services/packages/nuget/extract_metadata_file_service.rb42
-rw-r--r--app/services/packages/nuget/metadata_extraction_service.rb5
-rw-r--r--app/services/packages/nuget/odata_package_entry_service.rb39
-rw-r--r--app/services/packages/nuget/process_package_file_service.rb60
-rw-r--r--app/services/packages/nuget/symbols/create_symbol_files_service.rb69
-rw-r--r--app/services/packages/nuget/symbols/extract_symbol_signature_service.rb63
-rw-r--r--app/services/packages/nuget/update_package_from_metadata_service.rb39
-rw-r--r--app/services/packages/protection/create_rule_service.rb39
-rw-r--r--app/services/pages/migrate_from_legacy_storage_service.rb90
-rw-r--r--app/services/pages/migrate_legacy_storage_to_deployment_service.rb49
-rw-r--r--app/services/pages/zip_directory_service.rb97
-rw-r--r--app/services/projects/after_rename_service.rb17
-rw-r--r--app/services/projects/container_repository/cleanup_tags_base_service.rb2
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/services/projects/group_links/create_service.rb2
-rw-r--r--app/services/projects/hashed_storage/base_repository_service.rb115
-rw-r--r--app/services/projects/hashed_storage/migrate_repository_service.rb55
-rw-r--r--app/services/projects/hashed_storage/migration_service.rb9
-rw-r--r--app/services/projects/hashed_storage/rollback_attachments_service.rb29
-rw-r--r--app/services/projects/hashed_storage/rollback_repository_service.rb53
-rw-r--r--app/services/projects/hashed_storage/rollback_service.rb37
-rw-r--r--app/services/projects/import_service.rb4
-rw-r--r--app/services/projects/in_product_marketing_campaign_emails_service.rb53
-rw-r--r--app/services/projects/participants_service.rb4
-rw-r--r--app/services/projects/record_target_platforms_service.rb15
-rw-r--r--app/services/projects/transfer_service.rb32
-rw-r--r--app/services/projects/update_pages_service.rb5
-rw-r--r--app/services/projects/update_repository_storage_service.rb22
-rw-r--r--app/services/releases/create_service.rb2
-rw-r--r--app/services/releases/destroy_service.rb2
-rw-r--r--app/services/repositories/base_service.rb4
-rw-r--r--app/services/repositories/replicate_service.rb24
-rw-r--r--app/services/spam/spam_verdict_service.rb2
-rw-r--r--app/services/system_notes/issuables_service.rb13
-rw-r--r--app/services/tasks_to_be_done/base_service.rb55
-rw-r--r--app/services/tasks_to_be_done/create_ci_task_service.rb44
-rw-r--r--app/services/tasks_to_be_done/create_code_task_service.rb52
-rw-r--r--app/services/tasks_to_be_done/create_issues_task_service.rb43
-rw-r--r--app/services/todo_service.rb10
-rw-r--r--app/services/todos/destroy/base_service.rb31
-rw-r--r--app/services/todos/destroy/confidential_issue_service.rb39
-rw-r--r--app/services/todos/destroy/group_private_service.rb36
-rw-r--r--app/services/todos/destroy/project_private_service.rb23
-rw-r--r--app/services/todos/destroy/unauthorized_features_service.rb8
-rw-r--r--app/services/update_container_registry_info_service.rb5
-rw-r--r--app/services/users/allow_possible_spam_service.rb18
-rw-r--r--app/services/users/auto_ban_service.rb33
-rw-r--r--app/services/users/in_product_marketing_email_records.rb3
-rw-r--r--app/services/users/set_namespace_commit_email_service.rb2
-rw-r--r--app/services/users/signup_service.rb34
-rw-r--r--app/services/users/trust_service.rb (renamed from app/services/users/disallow_possible_spam_service.rb)5
-rw-r--r--app/services/users/untrust_service.rb14
-rw-r--r--app/services/verify_pages_domain_service.rb8
-rw-r--r--app/services/vs_code/settings/create_or_update_service.rb41
-rw-r--r--app/services/web_hook_service.rb6
-rw-r--r--app/services/work_items/related_work_item_links/create_service.rb21
-rw-r--r--app/services/work_items/widgets/labels_service/update_service.rb1
-rw-r--r--app/services/work_items/widgets/start_and_due_date_service/update_service.rb1
116 files changed, 1218 insertions, 1366 deletions
diff --git a/app/services/achievements/update_user_achievement_priorities_service.rb b/app/services/achievements/update_user_achievement_priorities_service.rb
new file mode 100644
index 00000000000..1165a1b3bf6
--- /dev/null
+++ b/app/services/achievements/update_user_achievement_priorities_service.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Achievements
+ class UpdateUserAchievementPrioritiesService
+ attr_reader :current_user, :user_achievements
+
+ def initialize(current_user, user_achievements)
+ @current_user = current_user
+ @user_achievements = user_achievements
+ end
+
+ def execute
+ return error_no_permissions unless allowed?
+
+ prioritized_user_achievements_map = Hash[user_achievements.map.with_index { |ua, idx| [ua.id, idx] }]
+
+ user_achievements_priorities_mapping = current_user.user_achievements.each_with_object({}) do |ua, result|
+ next if ua.priority.nil? && !prioritized_user_achievements_map.key?(ua.id)
+
+ result[ua] = { priority: prioritized_user_achievements_map.fetch(ua.id, nil) }
+ end
+
+ return ServiceResponse.success(payload: []) if user_achievements_priorities_mapping.empty?
+
+ ::Gitlab::Database::BulkUpdate.execute(%i[priority], user_achievements_priorities_mapping)
+
+ ServiceResponse.success(payload: user_achievements_priorities_mapping.keys.map(&:reload))
+ end
+
+ private
+
+ def allowed?
+ user_achievements.all? { |user_achievement| current_user&.can?(:update_owned_user_achievement, user_achievement) }
+ end
+
+ def error(message)
+ ServiceResponse.error(payload: user_achievements, message: Array(message))
+ end
+
+ def error_no_permissions
+ error("You can't update at least one of the given user achievements.")
+ end
+ end
+end
diff --git a/app/services/admin/abuse_reports/moderate_user_service.rb b/app/services/admin/abuse_reports/moderate_user_service.rb
index 823568d9db8..1e14806c694 100644
--- a/app/services/admin/abuse_reports/moderate_user_service.rb
+++ b/app/services/admin/abuse_reports/moderate_user_service.rb
@@ -42,6 +42,7 @@ module Admin
when :block_user then block_user
when :delete_user then delete_user
when :close_report then close_report
+ when :trust_user then trust_user
end
end
@@ -66,6 +67,10 @@ module Admin
success
end
+ def trust_user
+ Users::TrustService.new(current_user).execute(abuse_report.user)
+ end
+
def close_similar_open_reports
# admins see the abuse report and other open reports for the same user in one page
# hence, if the request is to close the report, close other open reports for the same user too
diff --git a/app/services/audit_events/build_service.rb b/app/services/audit_events/build_service.rb
index f5322fa5ff4..9eab2f836db 100644
--- a/app/services/audit_events/build_service.rb
+++ b/app/services/audit_events/build_service.rb
@@ -11,7 +11,10 @@ module AuditEvents
def initialize(
author:, scope:, target:, message:,
created_at: DateTime.current, additional_details: {}, ip_address: nil, target_details: nil)
- raise MissingAttributeError if missing_attribute?(author, scope, target, message)
+ raise MissingAttributeError, "author" if author.blank?
+ raise MissingAttributeError, "scope" if scope.blank?
+ raise MissingAttributeError, "target" if target.blank?
+ raise MissingAttributeError, "message" if message.blank?
@author = build_author(author)
@scope = scope
@@ -32,10 +35,6 @@ module AuditEvents
private
- def missing_attribute?(author, scope, target, message)
- author.blank? || scope.blank? || target.blank? || message.blank?
- end
-
def payload
base_payload.merge(details: base_details_payload)
end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 9b010272995..363510a41a1 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -39,32 +39,45 @@ module Auth
end
def self.full_access_token(*names)
- access_token(%w[*], names)
+ names_and_actions = names.index_with { %w[*] }
+ access_token(names_and_actions)
end
def self.import_access_token
- access_token(%w[*], ['import'], 'registry')
+ access_token({ 'import' => %w[*] }, 'registry')
end
def self.pull_access_token(*names)
- access_token(['pull'], names)
+ names_and_actions = names.index_with { %w[pull] }
+ access_token(names_and_actions)
end
def self.pull_nested_repositories_access_token(name)
- name = name.chomp('/') if name.end_with?('/')
- paths = [name, "#{name}/*"]
- access_token(['pull'], paths)
+ name = name.chomp('/')
+
+ access_token({
+ name => %w[pull],
+ "#{name}/*" => %w[pull]
+ })
+ end
+
+ def self.push_pull_nested_repositories_access_token(name)
+ name = name.chomp('/')
+
+ access_token({
+ name => %w[pull push],
+ "#{name}/*" => %w[pull]
+ })
end
- def self.access_token(actions, names, type = 'repository')
- names = names.flatten
+ def self.access_token(names_and_actions, type = 'repository')
registry = Gitlab.config.registry
token = JSONWebToken::RSAToken.new(registry.key)
token.issuer = registry.issuer
token.audience = AUDIENCE
token.expire_time = token_expire_at
- token[:access] = names.map do |name|
+ token[:access] = names_and_actions.map do |name, actions|
{
type: type,
name: name,
@@ -219,7 +232,6 @@ module Auth
# Overridden in EE
def can_access?(requested_project, requested_action)
return false unless requested_project.container_registry_enabled?
- return false if requested_project.repository_access_level == ::ProjectFeature::DISABLED
case requested_action
when 'pull'
diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb
index 77ed0369624..d0fde43138a 100644
--- a/app/services/auto_merge/base_service.rb
+++ b/app/services/auto_merge/base_service.rb
@@ -61,9 +61,9 @@ module AutoMerge
merge_request.can_be_merged_by?(current_user) &&
merge_request.open? &&
!merge_request.broken? &&
- !merge_request.draft? &&
- merge_request.mergeable_discussions_state? &&
- !merge_request.merge_blocked_by_other_mrs? &&
+ (skip_draft_check(merge_request) || !merge_request.draft?) &&
+ (skip_discussions_check(merge_request) || merge_request.mergeable_discussions_state?) &&
+ (skip_blocked_check(merge_request) || !merge_request.merge_blocked_by_other_mrs?) &&
yield
end
end
@@ -109,5 +109,20 @@ module AutoMerge
def track_exception(error, merge_request)
Gitlab::ErrorTracking.track_exception(error, merge_request_id: merge_request&.id)
end
+
+ # Will skip the draft check or not when checking if strategy is available
+ def skip_draft_check(merge_request)
+ false
+ end
+
+ # Will skip the blocked check or not when checking if strategy is available
+ def skip_blocked_check(merge_request)
+ false
+ end
+
+ # Will skip the discussions check or not when checking if strategy is available
+ def skip_discussions_check(merge_request)
+ false
+ end
end
end
diff --git a/app/services/branches/delete_service.rb b/app/services/branches/delete_service.rb
index 6efbdd161a1..e396d784ca6 100644
--- a/app/services/branches/delete_service.rb
+++ b/app/services/branches/delete_service.rb
@@ -37,3 +37,5 @@ module Branches
end
end
end
+
+Branches::DeleteService.prepend_mod
diff --git a/app/services/bulk_create_integration_service.rb b/app/services/bulk_create_integration_service.rb
index 8fbb7f4f347..70c77444f13 100644
--- a/app/services/bulk_create_integration_service.rb
+++ b/app/services/bulk_create_integration_service.rb
@@ -10,10 +10,10 @@ class BulkCreateIntegrationService
end
def execute
- service_list = ServiceList.new(batch, integration_hash(:create), association).to_array
+ integration_list = Integrations::IntegrationList.new(batch, integration_hash(:create), association).to_array
Integration.transaction do
- results = bulk_insert(*service_list)
+ results = bulk_insert(*integration_list)
if integration.data_fields_present?
data_list = DataList.new(results, data_fields_hash(:create), integration.data_fields.class).to_array
diff --git a/app/services/bulk_imports/process_service.rb b/app/services/bulk_imports/process_service.rb
new file mode 100644
index 00000000000..14c5545cfd5
--- /dev/null
+++ b/app/services/bulk_imports/process_service.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+module BulkImports
+ class ProcessService
+ PERFORM_DELAY = 5.seconds
+ DEFAULT_BATCH_SIZE = 5
+
+ attr_reader :bulk_import
+
+ def initialize(bulk_import)
+ @bulk_import = bulk_import
+ end
+
+ def execute
+ return unless bulk_import
+ return if bulk_import.completed?
+ return bulk_import.fail_op! if all_entities_failed?
+ return bulk_import.finish! if all_entities_processed? && bulk_import.started?
+ return re_enqueue if max_batch_size_exceeded? # Do not start more jobs if max allowed are already running
+
+ process_bulk_import
+ re_enqueue
+ rescue StandardError => e
+ Gitlab::ErrorTracking.track_exception(e, bulk_import_id: bulk_import.id)
+
+ bulk_import.fail_op
+ end
+
+ private
+
+ def process_bulk_import
+ bulk_import.start! if bulk_import.created?
+
+ created_entities.first(next_batch_size).each do |entity|
+ create_tracker(entity)
+
+ entity.start!
+
+ BulkImports::ExportRequestWorker.perform_async(entity.id)
+ end
+ end
+
+ def entities
+ @entities ||= bulk_import.entities
+ end
+
+ def created_entities
+ entities.with_status(:created)
+ end
+
+ def all_entities_processed?
+ entities.all? { |entity| entity.finished? || entity.failed? }
+ end
+
+ def all_entities_failed?
+ entities.all?(&:failed?)
+ end
+
+ # A new BulkImportWorker job is enqueued to either
+ # - Process the new BulkImports::Entity created during import (e.g. for the subgroups)
+ # - Or to mark the `bulk_import` as finished
+ def re_enqueue
+ BulkImportWorker.perform_in(PERFORM_DELAY, bulk_import.id)
+ end
+
+ def started_entities
+ entities.with_status(:started)
+ end
+
+ def max_batch_size_exceeded?
+ started_entities.count >= DEFAULT_BATCH_SIZE
+ end
+
+ def next_batch_size
+ [DEFAULT_BATCH_SIZE - started_entities.count, 0].max
+ end
+
+ def create_tracker(entity)
+ entity.class.transaction do
+ entity.pipelines.each do |pipeline|
+ status = skip_pipeline?(pipeline, entity) ? :skipped : :created
+
+ entity.trackers.create!(
+ stage: pipeline[:stage],
+ pipeline_name: pipeline[:pipeline],
+ status: BulkImports::Tracker.state_machine.states[status].value
+ )
+ end
+ end
+ end
+
+ def skip_pipeline?(pipeline, entity)
+ return false unless entity.source_version.valid?
+
+ minimum_version, maximum_version = pipeline.values_at(:minimum_source_version, :maximum_source_version)
+
+ if source_version_out_of_range?(minimum_version, maximum_version, entity.source_version.without_patch)
+ log_skipped_pipeline(pipeline, entity, minimum_version, maximum_version)
+ return true
+ end
+
+ false
+ end
+
+ def source_version_out_of_range?(minimum_version, maximum_version, non_patch_source_version)
+ (minimum_version && non_patch_source_version < Gitlab::VersionInfo.parse(minimum_version)) ||
+ (maximum_version && non_patch_source_version > Gitlab::VersionInfo.parse(maximum_version))
+ end
+
+ def log_skipped_pipeline(pipeline, entity, minimum_version, maximum_version)
+ logger.info(
+ message: 'Pipeline skipped as source instance version not compatible with pipeline',
+ bulk_import_entity_id: entity.id,
+ bulk_import_id: entity.bulk_import_id,
+ bulk_import_entity_type: entity.source_type,
+ source_full_path: entity.source_full_path,
+ pipeline_name: pipeline[:pipeline],
+ minimum_source_version: minimum_version,
+ maximum_source_version: maximum_version,
+ source_version: entity.source_version.to_s,
+ importer: 'gitlab_migration'
+ )
+ end
+
+ def logger
+ @logger ||= Gitlab::Import::Logger.build
+ end
+ end
+end
diff --git a/app/services/bulk_imports/relation_batch_export_service.rb b/app/services/bulk_imports/relation_batch_export_service.rb
index 19eb550216d..c7164d7c304 100644
--- a/app/services/bulk_imports/relation_batch_export_service.rb
+++ b/app/services/bulk_imports/relation_batch_export_service.rb
@@ -14,6 +14,7 @@ module BulkImports
start_batch!
export_service.export_batch(relation_batch_ids)
+ ensure_export_file_exists!
compress_exported_relation
upload_compressed_file
@@ -76,5 +77,15 @@ module BulkImports
batch.update!(status_event: 'fail_op', error: exception.message.truncate(255))
end
+
+ def exported_filepath
+ File.join(export_path, exported_filename)
+ end
+
+ # Create empty file on disk
+ # if relation is empty and nothing was exported
+ def ensure_export_file_exists!
+ FileUtils.touch(exported_filepath)
+ end
end
end
diff --git a/app/services/bulk_imports/relation_export_service.rb b/app/services/bulk_imports/relation_export_service.rb
index ed71c09420b..91640496440 100644
--- a/app/services/bulk_imports/relation_export_service.rb
+++ b/app/services/bulk_imports/relation_export_service.rb
@@ -18,6 +18,7 @@ module BulkImports
find_or_create_export! do |export|
export.remove_existing_upload!
export_service.execute
+ ensure_export_file_exists!
compress_exported_relation
upload_compressed_file(export)
end
@@ -91,5 +92,15 @@ module BulkImports
export&.update(status_event: 'fail_op', error: exception.class, batched: false)
end
+
+ def exported_filepath
+ File.join(export_path, export_service.exported_filename)
+ end
+
+ # Create empty file on disk
+ # if relation is empty and nothing was exported
+ def ensure_export_file_exists!
+ FileUtils.touch(exported_filepath)
+ end
end
end
diff --git a/app/services/chat_names/find_user_service.rb b/app/services/chat_names/find_user_service.rb
index 3b204d51bab..a7dc6a47a6b 100644
--- a/app/services/chat_names/find_user_service.rb
+++ b/app/services/chat_names/find_user_service.rb
@@ -11,7 +11,7 @@ module ChatNames
chat_name = find_chat_name
return unless chat_name
- chat_name.update_last_used_at
+ record_chat_activity(chat_name)
chat_name
end
@@ -27,5 +27,10 @@ module ChatNames
)
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def record_chat_activity(chat_name)
+ chat_name.update_last_used_at
+ Users::ActivityService.new(author: chat_name.user).execute
+ end
end
end
diff --git a/app/services/ci/catalog/resources/validate_service.rb b/app/services/ci/catalog/resources/validate_service.rb
new file mode 100644
index 00000000000..9e8986ba6fc
--- /dev/null
+++ b/app/services/ci/catalog/resources/validate_service.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Ci
+ module Catalog
+ module Resources
+ class ValidateService
+ attr_reader :project
+
+ def initialize(project, ref)
+ @project = project
+ @ref = ref
+ @errors = []
+ end
+
+ def execute
+ check_project_readme
+ check_project_description
+
+ if errors.empty?
+ ServiceResponse.success
+ else
+ ServiceResponse.error(message: errors.join(' , '))
+ end
+ end
+
+ private
+
+ attr_reader :ref, :errors
+
+ def check_project_description
+ return if project.description.present?
+
+ errors << 'Project must have a description'
+ end
+
+ def check_project_readme
+ return if project_has_readme?
+
+ errors << 'Project must have a README'
+ end
+
+ def project_has_readme?
+ project.repository.blob_data_at(ref, 'README.md')
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/ci/catalog/validate_resource_service.rb b/app/services/ci/catalog/validate_resource_service.rb
deleted file mode 100644
index f166c220869..00000000000
--- a/app/services/ci/catalog/validate_resource_service.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-module Ci
- module Catalog
- class ValidateResourceService
- attr_reader :project
-
- def initialize(project, ref)
- @project = project
- @ref = ref
- @errors = []
- end
-
- def execute
- check_project_readme
- check_project_description
-
- if errors.empty?
- ServiceResponse.success
- else
- ServiceResponse.error(message: errors.join(' , '))
- end
- end
-
- private
-
- attr_reader :ref, :errors
-
- def check_project_description
- return if project.description.present?
-
- errors << 'Project must have a description'
- end
-
- def check_project_readme
- return if project_has_readme?
-
- errors << 'Project must have a README'
- end
-
- def project_has_readme?
- project.repository.blob_data_at(ref, 'README.md')
- end
- end
- end
-end
diff --git a/app/services/ci/components/fetch_service.rb b/app/services/ci/components/fetch_service.rb
index 45abb415174..4f09d47b530 100644
--- a/app/services/ci/components/fetch_service.rb
+++ b/app/services/ci/components/fetch_service.rb
@@ -5,8 +5,6 @@ module Ci
class FetchService
include Gitlab::Utils::StrongMemoize
- TEMPLATE_FILE = 'template.yml'
-
COMPONENT_PATHS = [
::Gitlab::Ci::Components::InstancePath
].freeze
@@ -23,11 +21,16 @@ module Ci
reason: :unsupported_path)
end
- component_path = component_path_class.new(address: address, content_filename: TEMPLATE_FILE)
- content = component_path.fetch_content!(current_user: current_user)
+ component_path = component_path_class.new(address: address)
+ result = component_path.fetch_content!(current_user: current_user)
- if content.present?
- ServiceResponse.success(payload: { content: content, path: component_path })
+ if result
+ ServiceResponse.success(payload: {
+ content: result.content,
+ path: result.path,
+ project: component_path.project,
+ sha: component_path.sha
+ })
else
ServiceResponse.error(message: "#{error_prefix} content not found", reason: :content_not_found)
end
diff --git a/app/services/ci/job_artifacts/destroy_all_expired_service.rb b/app/services/ci/job_artifacts/destroy_all_expired_service.rb
index 57b95e59d7d..4d688d79982 100644
--- a/app/services/ci/job_artifacts/destroy_all_expired_service.rb
+++ b/app/services/ci/job_artifacts/destroy_all_expired_service.rb
@@ -44,10 +44,6 @@ module Ci
def destroy_batch(artifacts)
Ci::JobArtifacts::DestroyBatchService.new(artifacts, skip_projects_on_refresh: true).execute
end
-
- def loop_timeout?
- Time.current > @start_at + LOOP_TIMEOUT
- end
end
end
end
diff --git a/app/services/ci/pipeline_creation/cancel_redundant_pipelines_service.rb b/app/services/ci/pipeline_creation/cancel_redundant_pipelines_service.rb
index 05cd20a152b..c18984953a1 100644
--- a/app/services/ci/pipeline_creation/cancel_redundant_pipelines_service.rb
+++ b/app/services/ci/pipeline_creation/cancel_redundant_pipelines_service.rb
@@ -19,20 +19,11 @@ module Ci
return if pipeline.parent_pipeline? # skip if child pipeline
return unless project.auto_cancel_pending_pipelines?
- if Feature.enabled?(:use_offset_pagination_for_canceling_redundant_pipelines, project)
- paginator.each do |ids|
- pipelines = parent_and_child_pipelines(ids)
+ paginator.each do |ids|
+ pipelines = parent_and_child_pipelines(ids)
- Gitlab::OptimisticLocking.retry_lock(pipelines, name: 'cancel_pending_pipelines') do |cancelables|
- auto_cancel_interruptible_pipelines(cancelables.ids)
- end
- end
- else
- Gitlab::OptimisticLocking
- .retry_lock(parent_and_child_pipelines, name: 'cancel_pending_pipelines') do |cancelables|
- cancelables.select(:id).each_batch(of: BATCH_SIZE) do |cancelables_batch|
- auto_cancel_interruptible_pipelines(cancelables_batch.ids)
- end
+ Gitlab::OptimisticLocking.retry_lock(pipelines, name: 'cancel_pending_pipelines') do |cancelables|
+ auto_cancel_interruptible_pipelines(cancelables.ids)
end
end
end
@@ -61,7 +52,7 @@ module Ci
end
end
- def parent_auto_cancelable_pipelines(ids = nil)
+ def parent_auto_cancelable_pipelines(ids)
scope = project.all_pipelines
.created_after(pipelines_created_after)
.for_ref(pipeline.ref)
@@ -70,11 +61,10 @@ module Ci
.for_status(CommitStatus::AVAILABLE_STATUSES) # Force usage of project_id_and_status_and_created_at_index
.ci_sources
- scope = scope.id_in(ids) if ids.present?
- scope
+ scope.id_in(ids)
end
- def parent_and_child_pipelines(ids = nil)
+ def parent_and_child_pipelines(ids)
Ci::Pipeline.object_hierarchy(parent_auto_cancelable_pipelines(ids), project_condition: :same)
.base_and_descendants
.alive_or_scheduled
diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb
index 750272c3ecb..84e5089b0d5 100644
--- a/app/services/ci/pipeline_processing/atomic_processing_service.rb
+++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb
@@ -93,6 +93,8 @@ module Ci
# We do not continue to process the job if the previous status is not completed
return unless Ci::HasStatus::COMPLETED_STATUSES.include?(previous_status)
+ ::Deployments::CreateForJobService.new.execute(job)
+
Gitlab::OptimisticLocking.retry_lock(job, name: 'atomic_processing_update_job') do |subject|
Ci::ProcessBuildService.new(project, subject.user)
.execute(subject, previous_status)
diff --git a/app/services/ci/refs/enqueue_pipelines_to_unlock_service.rb b/app/services/ci/refs/enqueue_pipelines_to_unlock_service.rb
new file mode 100644
index 00000000000..319186ce030
--- /dev/null
+++ b/app/services/ci/refs/enqueue_pipelines_to_unlock_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Ci
+ module Refs
+ class EnqueuePipelinesToUnlockService
+ include BaseServiceUtility
+
+ BATCH_SIZE = 50
+ ENQUEUE_INTERVAL_SECONDS = 0.1
+
+ def execute(ci_ref, before_pipeline: nil)
+ pipelines_scope = ci_ref.pipelines.artifacts_locked
+ pipelines_scope = pipelines_scope.before_pipeline(before_pipeline) if before_pipeline
+ total_new_entries = 0
+
+ pipelines_scope.each_batch(of: BATCH_SIZE) do |batch|
+ pipeline_ids = batch.pluck(:id) # rubocop: disable CodeReuse/ActiveRecord
+ total_added = Ci::UnlockPipelineRequest.enqueue(pipeline_ids)
+ total_new_entries += total_added
+
+ # Take a little rest to avoid overloading Redis
+ sleep ENQUEUE_INTERVAL_SECONDS
+ end
+
+ success(
+ total_pending_entries: Ci::UnlockPipelineRequest.total_pending,
+ total_new_entries: total_new_entries
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/ci/retry_job_service.rb b/app/services/ci/retry_job_service.rb
index 14ea09f17a0..d7c3e9e7f64 100644
--- a/app/services/ci/retry_job_service.rb
+++ b/app/services/ci/retry_job_service.rb
@@ -39,7 +39,9 @@ module Ci
::Ci::CopyCrossDatabaseAssociationsService.new.execute(job, new_job)
- ::Deployments::CreateForJobService.new.execute(new_job)
+ if Feature.disabled?(:create_deployment_only_for_processable_jobs, project)
+ ::Deployments::CreateForJobService.new.execute(new_job)
+ end
::MergeRequests::AddTodoWhenBuildFailsService
.new(project: project)
diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb
index 85f910d05d7..f6b2c90c6ec 100644
--- a/app/services/ci/retry_pipeline_service.rb
+++ b/app/services/ci/retry_pipeline_service.rb
@@ -26,9 +26,7 @@ module Ci
.new(project: project, current_user: current_user)
.close_all(pipeline)
- Ci::ProcessPipelineService
- .new(pipeline)
- .execute
+ start_pipeline(pipeline)
ServiceResponse.success
rescue Gitlab::Access::AccessDeniedError => e
@@ -52,6 +50,10 @@ module Ci
def can_be_retried?(build)
can?(current_user, :update_build, build)
end
+
+ def start_pipeline(pipeline)
+ Ci::PipelineCreation::StartPipelineService.new(pipeline).execute
+ end
end
end
diff --git a/app/services/ci/unlock_pipeline_service.rb b/app/services/ci/unlock_pipeline_service.rb
new file mode 100644
index 00000000000..88d4a8fd0be
--- /dev/null
+++ b/app/services/ci/unlock_pipeline_service.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+module Ci
+ class UnlockPipelineService
+ include BaseServiceUtility
+ include ::Gitlab::ExclusiveLeaseHelpers
+
+ ExecutionTimeoutError = Class.new(StandardError)
+
+ BATCH_SIZE = 100
+ MAX_EXEC_DURATION = 10.minutes.freeze
+ LOCK_TIMEOUT = (MAX_EXEC_DURATION + 1.minute).freeze
+
+ def initialize(pipeline)
+ @pipeline = pipeline
+ @already_leased = false
+ @already_unlocked = false
+ @exec_timeout = false
+ @unlocked_job_artifacts_count = 0
+ @unlocked_pipeline_artifacts_count = 0
+ end
+
+ def execute
+ unlock_pipeline_exclusively
+
+ success(
+ skipped_already_leased: already_leased,
+ skipped_already_unlocked: already_unlocked,
+ exec_timeout: exec_timeout,
+ unlocked_job_artifacts: unlocked_job_artifacts_count,
+ unlocked_pipeline_artifacts: unlocked_pipeline_artifacts_count
+ )
+ end
+
+ private
+
+ attr_reader :pipeline, :already_leased, :already_unlocked, :exec_timeout,
+ :unlocked_job_artifacts_count, :unlocked_pipeline_artifacts_count
+
+ def unlock_pipeline_exclusively
+ in_lock(lock_key, ttl: LOCK_TIMEOUT, retries: 0) do
+ # Even though we enforce uniqueness when enqueueing pipelines, there is still a rare race condition chance that
+ # a pipeline can be re-enqueued right after a worker pops off the same pipeline ID from the queue, and then just
+ # after it completing the unlock process and releasing the lock, another worker picks up the re-enqueued
+ # pipeline ID. So let's make sure to only unlock artifacts if the pipeline has not been unlocked.
+ if pipeline.unlocked?
+ @already_unlocked = true
+ break
+ end
+
+ unlock_job_artifacts
+ unlock_pipeline_artifacts
+
+ # Marking the row in `ci_pipelines` to unlocked signifies that all artifacts have
+ # already been unlocked. This must always happen last.
+ unlock_pipeline
+ end
+ rescue ExecutionTimeoutError
+ @exec_timeout = true
+ rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ @already_leased = true
+ ensure
+ if pipeline.unlocked?
+ Ci::UnlockPipelineRequest.log_event(:completed, pipeline.id) unless already_unlocked
+ else
+ # This is to ensure to re-enqueue the pipeline in 2 occasions:
+ # 1. When an unexpected error happens.
+ # 2. When the execution timeout has been reached in the case of a pipeline having a lot of
+ # job artifacts. This allows us to continue unlocking the rest of the artifacts from
+ # where we left off. This is why we unlock the pipeline last.
+ Ci::UnlockPipelineRequest.enqueue(pipeline.id)
+ Ci::UnlockPipelineRequest.log_event(:re_enqueued, pipeline.id)
+ end
+ end
+
+ def lock_key
+ "ci:unlock_pipeline_service:lock:#{pipeline.id}"
+ end
+
+ def unlock_pipeline
+ pipeline.update_column(:locked, Ci::Pipeline.lockeds[:unlocked])
+ end
+
+ def unlock_job_artifacts
+ start = Time.current
+
+ pipeline.builds.each_batch(of: BATCH_SIZE) do |builds|
+ # rubocop: disable CodeReuse/ActiveRecord
+ Ci::JobArtifact.where(job_id: builds.pluck(:id)).each_batch(of: BATCH_SIZE) do |job_artifacts|
+ unlocked_count = Ci::JobArtifact
+ .where(id: job_artifacts.pluck(:id))
+ .update_all(locked: :unlocked)
+
+ @unlocked_job_artifacts_count ||= 0
+ @unlocked_job_artifacts_count += unlocked_count
+
+ raise ExecutionTimeoutError if (Time.current - start) > MAX_EXEC_DURATION
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+
+ def unlock_pipeline_artifacts
+ @unlocked_pipeline_artifacts_count = pipeline.pipeline_artifacts.update_all(locked: :unlocked)
+ end
+ end
+end
diff --git a/app/services/clusters/agent_tokens/revoke_service.rb b/app/services/clusters/agent_tokens/revoke_service.rb
index 5d89b405969..46873fbbc47 100644
--- a/app/services/clusters/agent_tokens/revoke_service.rb
+++ b/app/services/clusters/agent_tokens/revoke_service.rb
@@ -13,7 +13,7 @@ module Clusters
def execute
return error_no_permissions unless current_user.can?(:create_cluster, token.agent.project)
- if token.update(status: token.class.statuses[:revoked])
+ if token.revoke!
log_activity_event(token)
ServiceResponse.success
diff --git a/app/services/clusters/cleanup/project_namespace_service.rb b/app/services/clusters/cleanup/project_namespace_service.rb
index f6ac06d0594..3d5a4f85d10 100644
--- a/app/services/clusters/cleanup/project_namespace_service.rb
+++ b/app/services/clusters/cleanup/project_namespace_service.rb
@@ -40,7 +40,7 @@ module Clusters
cluster.kubeclient&.delete_namespace(kubernetes_namespace.namespace)
rescue Kubeclient::ResourceNotFoundError
# The resources have already been deleted, possibly on a previous attempt that timed out
- rescue Gitlab::UrlBlocker::BlockedUrlError
+ rescue Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError
# User gave an invalid cluster from the start, or deleted the endpoint before this job ran
end
end
diff --git a/app/services/clusters/cleanup/service_account_service.rb b/app/services/clusters/cleanup/service_account_service.rb
index 0ce4bf9bb9c..0358a5412b3 100644
--- a/app/services/clusters/cleanup/service_account_service.rb
+++ b/app/services/clusters/cleanup/service_account_service.rb
@@ -22,7 +22,7 @@ module Clusters
)
rescue Kubeclient::ResourceNotFoundError
# The resources have already been deleted, possibly on a previous attempt that timed out
- rescue Gitlab::UrlBlocker::BlockedUrlError
+ rescue Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError
# User gave an invalid cluster from the start, or deleted the endpoint before this job ran
rescue Kubeclient::HttpError => e
# unauthorized, forbidden: GitLab's access has been revoked
diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb
index 89370bd8abb..5fc84e5aad7 100644
--- a/app/services/commits/create_service.rb
+++ b/app/services/commits/create_service.rb
@@ -40,11 +40,7 @@ module Commits
Gitlab::Git::CommandError => ex
Gitlab::ErrorTracking.log_exception(ex)
- if Feature.enabled?(:errors_utf_8_encoding)
- error(Gitlab::EncodingHelper.encode_utf8_no_detect(ex.message))
- else
- error(ex.message)
- end
+ error(Gitlab::EncodingHelper.encode_utf8_no_detect(ex.message))
end
private
diff --git a/app/services/concerns/update_repository_storage_methods.rb b/app/services/concerns/update_repository_storage_methods.rb
index f14c79ecd7e..50963cc58b2 100644
--- a/app/services/concerns/update_repository_storage_methods.rb
+++ b/app/services/concerns/update_repository_storage_methods.rb
@@ -82,7 +82,6 @@ module UpdateRepositoryStorageMethods
repository = type.repository_for(type.design? ? container.design_management_repository : container)
full_path = repository.full_path
raw_repository = repository.raw
- checksum = repository.checksum
# Initialize a git repository on the target path
new_repository = Gitlab::Git::Repository.new(
@@ -92,12 +91,7 @@ module UpdateRepositoryStorageMethods
full_path
)
- new_repository.replicate(raw_repository)
- new_checksum = new_repository.checksum
-
- if checksum != new_checksum
- raise Error, s_('UpdateRepositoryStorage|Failed to verify %{type} repository checksum from %{old} to %{new}') % { type: type.name, old: checksum, new: new_checksum }
- end
+ Repositories::ReplicateService.new(raw_repository).execute(new_repository, type.name)
end
def same_filesystem?
diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb
index 1a03b444b68..a54c4947b0b 100644
--- a/app/services/concerns/users/participable_service.rb
+++ b/app/services/concerns/users/participable_service.rb
@@ -34,7 +34,7 @@ module Users
def groups
return [] unless current_user
- current_user.authorized_groups.with_route.sort_by(&:path)
+ current_user.authorized_groups.with_route.sort_by(&:full_path)
end
def render_participants_as_hash(participants)
diff --git a/app/services/deployments/create_for_job_service.rb b/app/services/deployments/create_for_job_service.rb
index e230515ce27..fb07efe8694 100644
--- a/app/services/deployments/create_for_job_service.rb
+++ b/app/services/deployments/create_for_job_service.rb
@@ -38,8 +38,6 @@ module Deployments
return unless deployment.valid? && deployment.environment.persisted?
if cluster = deployment.environment.deployment_platform&.cluster # rubocop: disable Lint/AssignmentInCondition
- # double write cluster_id until 12.9: https://gitlab.com/gitlab-org/gitlab/issues/202628
- deployment.cluster_id = cluster.id
deployment.deployment_cluster = ::DeploymentCluster.new(
cluster_id: cluster.id,
kubernetes_namespace: cluster.kubernetes_namespace_for(deployment.environment, deployable: job)
diff --git a/app/services/deployments/create_service.rb b/app/services/deployments/create_service.rb
index ebf2b077bca..8ef9982f41b 100644
--- a/app/services/deployments/create_service.rb
+++ b/app/services/deployments/create_service.rb
@@ -28,7 +28,6 @@ module Deployments
# We use explicit parameters here so we never by accident allow parameters
# to be set that one should not be able to set (e.g. the row ID).
{
- cluster_id: environment.deployment_platform&.cluster_id,
project_id: environment.project_id,
environment_id: environment.id,
ref: params[:ref],
diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb
index 31da099d078..a2eb4f1f396 100644
--- a/app/services/git/branch_hooks_service.rb
+++ b/app/services/git/branch_hooks_service.rb
@@ -156,7 +156,7 @@ module Git
return if branch_to_sync.nil? && commits_to_sync.empty?
- if commits_to_sync.any? && Feature.enabled?(:batch_delay_jira_branch_sync_worker, project)
+ if commits_to_sync.any?
commits_to_sync.each_slice(JIRA_SYNC_BATCH_SIZE).with_index do |commits, i|
JiraConnect::SyncBranchWorker.perform_in(
JIRA_SYNC_BATCH_DELAY * i,
diff --git a/app/services/import/bitbucket_server_service.rb b/app/services/import/bitbucket_server_service.rb
index 3d961780889..e628e88eaa9 100644
--- a/app/services/import/bitbucket_server_service.rb
+++ b/app/services/import/bitbucket_server_service.rb
@@ -42,7 +42,8 @@ module Import
project_name,
target_namespace,
current_user,
- credentials
+ credentials,
+ timeout_strategy
).execute
end
@@ -74,6 +75,10 @@ module Import
@url ||= params[:bitbucket_server_url]
end
+ def timeout_strategy
+ @timeout_strategy ||= params[:timeout_strategy] || ProjectImportData::PESSIMISTIC_TIMEOUT
+ end
+
def allow_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
index 73e0c229a9c..86c62145a87 100644
--- a/app/services/import/github_service.rb
+++ b/app/services/import/github_service.rb
@@ -138,6 +138,7 @@ module Import
Gitlab::GithubImport::Settings
.new(project)
.write(
+ timeout_strategy: params[:timeout_strategy] || ProjectImportData::PESSIMISTIC_TIMEOUT,
optional_stages: params[:optional_stages],
additional_access_tokens: access_params[:additional_access_tokens]
)
diff --git a/app/services/import/validate_remote_git_endpoint_service.rb b/app/services/import/validate_remote_git_endpoint_service.rb
index a994072c4aa..2177238fddf 100644
--- a/app/services/import/validate_remote_git_endpoint_service.rb
+++ b/app/services/import/validate_remote_git_endpoint_service.rb
@@ -13,6 +13,8 @@ module Import
GIT_PROTOCOL_PKT_LEN = 4
GIT_MINIMUM_RESPONSE_LENGTH = GIT_PROTOCOL_PKT_LEN + GIT_EXPECTED_FIRST_PACKET_LINE.length
EXPECTED_CONTENT_TYPE = "application/x-#{GIT_SERVICE_NAME}-advertisement"
+ INVALID_BODY_MESSAGE = 'Not a git repository: Invalid response body'
+ INVALID_CONTENT_TYPE_MESSAGE = 'Not a git repository: Invalid content-type'
def initialize(params)
@params = params
@@ -30,32 +32,35 @@ module Import
uri.fragment = nil
url = Gitlab::Utils.append_path(uri.to_s, "/info/refs?service=#{GIT_SERVICE_NAME}")
- response_body = ''
- result = nil
- Gitlab::HTTP.try_get(url, stream_body: true, follow_redirects: false, basic_auth: auth) do |fragment|
- response_body += fragment
- next if response_body.length < GIT_MINIMUM_RESPONSE_LENGTH
-
- result = if status_code_is_valid(fragment) && content_type_is_valid(fragment) && response_body_is_valid(response_body)
- :success
- else
- :error
- end
-
- # We are interested only in the first chunks of the response
- # So we're using stream_body: true and breaking when receive enough body
- break
- end
+ response, response_body = http_get_and_extract_first_chunks(url)
- if result == :success
- ServiceResponse.success
- else
- ServiceResponse.error(message: "#{uri} is not a valid HTTP Git repository")
- end
+ validate(uri, response, response_body)
+ rescue *Gitlab::HTTP::HTTP_ERRORS => err
+ error_result("HTTP #{err.class.name.underscore} error: #{err.message}")
+ rescue StandardError => err
+ ServiceResponse.error(
+ message: "Internal #{err.class.name.underscore} error: #{err.message}",
+ reason: 500
+ )
end
private
+ def http_get_and_extract_first_chunks(url)
+ # We are interested only in the first chunks of the response
+ # So we're using stream_body: true and breaking when receive enough body
+ response = nil
+ response_body = ''
+
+ Gitlab::HTTP.get(url, stream_body: true, follow_redirects: false, basic_auth: auth) do |response_chunk|
+ response = response_chunk
+ response_body += response_chunk
+ break if GIT_MINIMUM_RESPONSE_LENGTH <= response_body.length
+ end
+
+ [response, response_body]
+ end
+
def auth
unless @params[:user].to_s.blank?
{
@@ -65,16 +70,38 @@ module Import
end
end
- def status_code_is_valid(fragment)
- fragment.http_response.code == '200'
+ def validate(uri, response, response_body)
+ return status_code_error(uri, response) unless status_code_is_valid?(response)
+ return error_result(INVALID_CONTENT_TYPE_MESSAGE) unless content_type_is_valid?(response)
+ return error_result(INVALID_BODY_MESSAGE) unless response_body_is_valid?(response_body)
+
+ ServiceResponse.success
+ end
+
+ def status_code_error(uri, response)
+ http_code = response.http_response.code.to_i
+ message = response.http_response.message || Rack::Utils::HTTP_STATUS_CODES[http_code]
+
+ error_result(
+ "#{uri} endpoint error: #{http_code}#{message.presence&.prepend(' ')}",
+ http_code
+ )
+ end
+
+ def error_result(message, reason = nil)
+ ServiceResponse.error(message: message, reason: reason)
+ end
+
+ def status_code_is_valid?(response)
+ response.http_response.code == '200'
end
- def content_type_is_valid(fragment)
- fragment.http_response['content-type'] == EXPECTED_CONTENT_TYPE
+ def content_type_is_valid?(response)
+ response.http_response['content-type'] == EXPECTED_CONTENT_TYPE
end
- def response_body_is_valid(response_body)
- response_body.match?(GIT_BODY_MESSAGE_REGEXP)
+ def response_body_is_valid?(response_body)
+ response_body.length <= GIT_MINIMUM_RESPONSE_LENGTH && response_body.match?(GIT_BODY_MESSAGE_REGEXP)
end
end
end
diff --git a/app/services/issuable/clone/base_service.rb b/app/services/issuable/clone/base_service.rb
index a4e815e70fc..d7fdd235db1 100644
--- a/app/services/issuable/clone/base_service.rb
+++ b/app/services/issuable/clone/base_service.rb
@@ -93,5 +93,3 @@ module Issuable
end
end
end
-
-Issuable::Clone::BaseService.prepend_mod_with('Issuable::Clone::BaseService')
diff --git a/app/services/issuable_links/create_service.rb b/app/services/issuable_links/create_service.rb
index 761ba3f74aa..c855a58522c 100644
--- a/app/services/issuable_links/create_service.rb
+++ b/app/services/issuable_links/create_service.rb
@@ -2,13 +2,14 @@
module IssuableLinks
class CreateService < BaseService
- attr_reader :issuable, :current_user, :params
+ attr_reader :issuable, :current_user, :params, :new_links
def initialize(issuable, user, params)
@issuable = issuable
@current_user = user
@params = params.dup
@errors = []
+ @new_links = []
end
def execute
@@ -45,6 +46,7 @@ module IssuableLinks
set_link_type(link)
if link.changed? && link.save
+ new_links << link
create_notes(link)
end
diff --git a/app/services/issue_links/create_service.rb b/app/services/issue_links/create_service.rb
index db05920678e..3523e945d37 100644
--- a/app/services/issue_links/create_service.rb
+++ b/app/services/issue_links/create_service.rb
@@ -9,7 +9,7 @@ module IssueLinks
end
def previous_related_issuables
- @related_issues ||= issuable.related_issues(current_user).to_a
+ @related_issues ||= issuable.related_issues(authorize: false).to_a
end
private
diff --git a/app/services/issues/set_crm_contacts_service.rb b/app/services/issues/set_crm_contacts_service.rb
index 5836097f1fd..c2ed7c554be 100644
--- a/app/services/issues/set_crm_contacts_service.rb
+++ b/app/services/issues/set_crm_contacts_service.rb
@@ -13,7 +13,7 @@ module Issues
return error_invalid_params unless valid_params?
@existing_ids = issue.customer_relations_contact_ids
- determine_changes if params[:replace_ids].present?
+ determine_changes if set_present?
return error_too_many if too_many?
@added_count = 0
@@ -108,7 +108,7 @@ module Issues
end
def set_present?
- params[:replace_ids].present?
+ !params[:replace_ids].nil?
end
def add_or_remove_present?
diff --git a/app/services/jira_connect/sync_service.rb b/app/services/jira_connect/sync_service.rb
index 497c282072d..5d9292a6967 100644
--- a/app/services/jira_connect/sync_service.rb
+++ b/app/services/jira_connect/sync_service.rb
@@ -9,6 +9,8 @@ module JiraConnect
# Parameters: see Atlassian::JiraConnect::Client#send_info
# Includes: update_sequence_id, commits, branches, merge_requests, pipelines
def execute(**args)
+ preload_reviewers_for_merge_requests(args[:merge_requests]) if args.key?(:merge_requests)
+
JiraConnectInstallation.for_project(project).flat_map do |installation|
client = Atlassian::JiraConnect::Client.new(installation.base_url, installation.shared_secret)
@@ -43,5 +45,11 @@ module JiraConnect
def logger
Gitlab::IntegrationsLogger
end
+
+ def preload_reviewers_for_merge_requests(merge_requests)
+ ActiveRecord::Associations::Preloader.new(
+ records: merge_requests, associations: [:approvals, { merge_request_reviewers: :reviewer }]
+ ).call
+ end
end
end
diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb
index aba075c3644..9cedc7ee3a5 100644
--- a/app/services/members/create_service.rb
+++ b/app/services/members/create_service.rb
@@ -16,7 +16,6 @@ module Members
@errors = []
@invites = invites_from_params
@source = params[:source]
- @tasks_to_be_done_members = []
end
def execute
@@ -31,13 +30,13 @@ module Members
validate_invitable!
add_members
- create_tasks_to_be_done
enqueue_onboarding_progress_action
publish_event!
result
rescue BlankInvitesError, TooManyInvitesError, MembershipLockedError => e
+ Gitlab::ErrorTracking.log_exception(e, class: self.class.to_s, user_id: current_user.id)
error(e.message)
end
@@ -47,8 +46,7 @@ module Members
private
- attr_reader :source, :errors, :invites, :member_created_namespace_id, :members,
- :tasks_to_be_done_members, :member_created_member_task_id
+ attr_reader :source, :errors, :invites, :member_created_namespace_id, :members
def adding_at_least_one_owner
params[:access_level] == Gitlab::Access::OWNER
@@ -88,9 +86,7 @@ module Members
invites,
params[:access_level],
expires_at: params[:expires_at],
- current_user: current_user,
- tasks_to_be_done: params[:tasks_to_be_done],
- tasks_project_id: params[:tasks_project_id]
+ current_user: current_user
)
members.each { |member| process_result(member) }
@@ -123,7 +119,6 @@ module Members
def after_execute(member:)
super
- build_tasks_to_be_done_members(member)
track_invite_source(member)
end
@@ -146,30 +141,6 @@ module Members
member.invite? ? 'net_new_user' : 'existing_user'
end
- def build_tasks_to_be_done_members(member)
- return unless tasks_to_be_done?(member)
-
- @tasks_to_be_done_members << member
- # We can take the first `member_task` here, since all tasks will have the same attributes needed
- # for the `TasksToBeDone::CreateWorker`, ie. `project` and `tasks_to_be_done`.
- @member_created_member_task_id ||= member.member_task.id
- end
-
- def tasks_to_be_done?(member)
- return false if params[:tasks_to_be_done].blank? || params[:tasks_project_id].blank?
-
- # Only create task issues for existing users. Tasks for new users are created when they signup.
- member.member_task&.valid? && member.user.present?
- end
-
- def create_tasks_to_be_done
- return unless member_created_member_task_id # signal if there is any work to be done here
-
- TasksToBeDone::CreateWorker.perform_async(member_created_member_task_id,
- current_user.id,
- tasks_to_be_done_members.map(&:user_id))
- end
-
def user_limit
limit = params.fetch(:limit, DEFAULT_INVITE_LIMIT)
diff --git a/app/services/members/creator_service.rb b/app/services/members/creator_service.rb
index cc18aae7446..22d8b30db18 100644
--- a/app/services/members/creator_service.rb
+++ b/app/services/members/creator_service.rb
@@ -53,8 +53,7 @@ module Members
common_arguments = {
source: source,
access_level: access_level,
- existing_members: existing_members,
- tasks_to_be_done: args[:tasks_to_be_done] || []
+ existing_members: existing_members
}.merge(parsed_args(args))
members = emails.map do |email|
@@ -81,7 +80,6 @@ module Members
{
current_user: args[:current_user],
expires_at: args[:expires_at],
- tasks_project_id: args[:tasks_project_id],
ldap: args[:ldap]
}
end
@@ -212,22 +210,7 @@ module Members
end
def after_commit_tasks
- create_member_task
- end
-
- def create_member_task
- return unless member.persisted?
- return if member_task_attributes.value?(nil)
- return if member.member_task.present?
-
- member.create_member_task(member_task_attributes)
- end
-
- def member_task_attributes
- {
- tasks_to_be_done: args[:tasks_to_be_done],
- project_id: args[:tasks_project_id]
- }
+ # hook for overriding in other uses
end
def approve_request
diff --git a/app/services/merge_requests/approval_service.rb b/app/services/merge_requests/approval_service.rb
index dbe5567cbc5..f9857cdad39 100644
--- a/app/services/merge_requests/approval_service.rb
+++ b/app/services/merge_requests/approval_service.rb
@@ -7,7 +7,7 @@ module MergeRequests
approval = merge_request.approvals.new(
user: current_user,
- patch_id_sha: fetch_patch_id_sha(merge_request)
+ patch_id_sha: merge_request.current_patch_id_sha
)
return success unless save_approval(approval)
@@ -36,17 +36,6 @@ module MergeRequests
private
- def fetch_patch_id_sha(merge_request)
- diff_refs = merge_request.diff_refs
- base_sha = diff_refs&.base_sha
- head_sha = diff_refs&.head_sha
-
- return unless base_sha && head_sha
- return if base_sha == head_sha
-
- merge_request.project.repository.get_patch_id(base_sha, head_sha)
- end
-
def eligible_for_approval?(merge_request)
merge_request.eligible_for_approval_by?(current_user)
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 29aba3c8679..89e5920a4fb 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -181,3 +181,5 @@ module MergeRequests
end
end
end
+
+MergeRequests::MergeService.prepend_mod
diff --git a/app/services/merge_requests/mergeability/check_base_service.rb b/app/services/merge_requests/mergeability/check_base_service.rb
index e614a7c27fe..e1c4d751296 100644
--- a/app/services/merge_requests/mergeability/check_base_service.rb
+++ b/app/services/merge_requests/mergeability/check_base_service.rb
@@ -9,6 +9,10 @@ module MergeRequests
@params = params
end
+ def self.identifier
+ failure_reason
+ end
+
def skip?
raise NotImplementedError
end
@@ -24,12 +28,22 @@ module MergeRequests
private
+ def failure_reason
+ self.class.failure_reason
+ end
+
def success(**args)
- Gitlab::MergeRequests::Mergeability::CheckResult.success(payload: args)
+ Gitlab::MergeRequests::Mergeability::CheckResult
+ .success(payload: default_payload(args))
end
def failure(**args)
- Gitlab::MergeRequests::Mergeability::CheckResult.failed(payload: args)
+ Gitlab::MergeRequests::Mergeability::CheckResult
+ .failed(payload: default_payload(args))
+ end
+
+ def default_payload(args)
+ args.merge(identifier: self.class.identifier)
end
end
end
diff --git a/app/services/merge_requests/mergeability/check_broken_status_service.rb b/app/services/merge_requests/mergeability/check_broken_status_service.rb
index 6fe4eb4a57f..25293c53bb5 100644
--- a/app/services/merge_requests/mergeability/check_broken_status_service.rb
+++ b/app/services/merge_requests/mergeability/check_broken_status_service.rb
@@ -2,6 +2,10 @@
module MergeRequests
module Mergeability
class CheckBrokenStatusService < CheckBaseService
+ def self.failure_reason
+ :broken_status
+ end
+
def execute
if merge_request.broken?
failure(reason: failure_reason)
@@ -17,12 +21,6 @@ module MergeRequests
def cacheable?
false
end
-
- private
-
- def failure_reason
- :broken_status
- end
end
end
end
diff --git a/app/services/merge_requests/mergeability/check_ci_status_service.rb b/app/services/merge_requests/mergeability/check_ci_status_service.rb
index 9e09b513c57..f7fa3259d97 100644
--- a/app/services/merge_requests/mergeability/check_ci_status_service.rb
+++ b/app/services/merge_requests/mergeability/check_ci_status_service.rb
@@ -2,6 +2,10 @@
module MergeRequests
module Mergeability
class CheckCiStatusService < CheckBaseService
+ def self.failure_reason
+ :ci_must_pass
+ end
+
def execute
if merge_request.mergeable_ci_state?
success
@@ -17,12 +21,6 @@ module MergeRequests
def cacheable?
false
end
-
- private
-
- def failure_reason
- :ci_must_pass
- end
end
end
end
diff --git a/app/services/merge_requests/mergeability/check_conflict_status_service.rb b/app/services/merge_requests/mergeability/check_conflict_status_service.rb
new file mode 100644
index 00000000000..2bc253322c9
--- /dev/null
+++ b/app/services/merge_requests/mergeability/check_conflict_status_service.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ module Mergeability
+ class CheckConflictStatusService < CheckBaseService
+ def self.failure_reason
+ :conflict
+ end
+
+ def execute
+ if merge_request.can_be_merged?
+ success
+ else
+ failure(reason: failure_reason)
+ end
+ end
+
+ def skip?
+ false
+ end
+
+ def cacheable?
+ false
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/mergeability/check_discussions_status_service.rb b/app/services/merge_requests/mergeability/check_discussions_status_service.rb
index 3421d96e8ae..34db5f8a944 100644
--- a/app/services/merge_requests/mergeability/check_discussions_status_service.rb
+++ b/app/services/merge_requests/mergeability/check_discussions_status_service.rb
@@ -2,6 +2,10 @@
module MergeRequests
module Mergeability
class CheckDiscussionsStatusService < CheckBaseService
+ def self.failure_reason
+ :discussions_not_resolved
+ end
+
def execute
if merge_request.mergeable_discussions_state?
success
@@ -17,12 +21,6 @@ module MergeRequests
def cacheable?
false
end
-
- private
-
- def failure_reason
- :discussions_not_resolved
- end
end
end
end
diff --git a/app/services/merge_requests/mergeability/check_draft_status_service.rb b/app/services/merge_requests/mergeability/check_draft_status_service.rb
index a1524317155..85b67fdc629 100644
--- a/app/services/merge_requests/mergeability/check_draft_status_service.rb
+++ b/app/services/merge_requests/mergeability/check_draft_status_service.rb
@@ -3,6 +3,10 @@
module MergeRequests
module Mergeability
class CheckDraftStatusService < CheckBaseService
+ def self.failure_reason
+ :draft_status
+ end
+
def execute
if merge_request.draft?
failure(reason: failure_reason)
@@ -12,18 +16,12 @@ module MergeRequests
end
def skip?
- false
+ params[:skip_draft_check].present?
end
def cacheable?
false
end
-
- private
-
- def failure_reason
- :draft_status
- end
end
end
end
diff --git a/app/services/merge_requests/mergeability/check_open_status_service.rb b/app/services/merge_requests/mergeability/check_open_status_service.rb
index 29f3d0d3ccb..f5b70f18394 100644
--- a/app/services/merge_requests/mergeability/check_open_status_service.rb
+++ b/app/services/merge_requests/mergeability/check_open_status_service.rb
@@ -3,6 +3,10 @@
module MergeRequests
module Mergeability
class CheckOpenStatusService < CheckBaseService
+ def self.failure_reason
+ :not_open
+ end
+
def execute
if merge_request.open?
success
@@ -18,12 +22,6 @@ module MergeRequests
def cacheable?
false
end
-
- private
-
- def failure_reason
- :not_open
- end
end
end
end
diff --git a/app/services/merge_requests/mergeability/check_rebase_status_service.rb b/app/services/merge_requests/mergeability/check_rebase_status_service.rb
new file mode 100644
index 00000000000..2163fec8bd6
--- /dev/null
+++ b/app/services/merge_requests/mergeability/check_rebase_status_service.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ module Mergeability
+ class CheckRebaseStatusService < CheckBaseService
+ def self.failure_reason
+ :need_rebase
+ end
+
+ def execute
+ if merge_request.should_be_rebased?
+ failure(reason: failure_reason)
+ else
+ success
+ end
+ end
+
+ def skip?
+ params[:skip_rebase_check].present?
+ end
+
+ def cacheable?
+ false
+ end
+ end
+ end
+end
diff --git a/app/services/merge_requests/mergeability/detailed_merge_status_service.rb b/app/services/merge_requests/mergeability/detailed_merge_status_service.rb
index 987d6ce8e9f..86c8122604c 100644
--- a/app/services/merge_requests/mergeability/detailed_merge_status_service.rb
+++ b/app/services/merge_requests/mergeability/detailed_merge_status_service.rb
@@ -24,7 +24,7 @@ module MergeRequests
ci_check_failure_reason
end
else
- check_results.failure_reason
+ check_results.payload[:failure_reason]
end
end
@@ -46,7 +46,11 @@ module MergeRequests
def check_results
strong_memoize(:check_results) do
- merge_request.execute_merge_checks(params: { skip_ci_check: true })
+ merge_request
+ .execute_merge_checks(
+ MergeRequest.mergeable_state_checks,
+ params: { skip_ci_check: true }
+ )
end
end
diff --git a/app/services/merge_requests/mergeability/run_checks_service.rb b/app/services/merge_requests/mergeability/run_checks_service.rb
index 740a6feac2c..5150c03d0a3 100644
--- a/app/services/merge_requests/mergeability/run_checks_service.rb
+++ b/app/services/merge_requests/mergeability/run_checks_service.rb
@@ -9,8 +9,8 @@ module MergeRequests
@params = params
end
- def execute
- @results = merge_request.mergeability_checks.each_with_object([]) do |check_class, result_hash|
+ def execute(checks, execute_all: false)
+ @results = checks.each_with_object([]) do |check_class, result_hash|
check = check_class.new(merge_request: merge_request, params: params)
next if check.skip?
@@ -21,24 +21,20 @@ module MergeRequests
result_hash << check_result
- break result_hash if check_result.failed?
+ break result_hash if check_result.failed? && !execute_all
end
logger.commit
- self
- end
-
- def success?
- raise 'Execute needs to be called before' if results.nil?
-
- results.all?(&:success?)
- end
-
- def failure_reason
- raise 'Execute needs to be called before' if results.nil?
+ return ServiceResponse.success(payload: { results: results }) if all_results_success?
- results.find(&:failed?)&.payload&.fetch(:reason)&.to_sym
+ ServiceResponse.error(
+ message: 'Checks failed.',
+ payload: {
+ results: results,
+ failure_reason: failure_reason
+ }
+ )
end
private
@@ -67,6 +63,14 @@ module MergeRequests
MergeRequests::Mergeability::Logger.new(merge_request: merge_request)
end
end
+
+ def all_results_success?
+ results.all?(&:success?)
+ end
+
+ def failure_reason
+ results.find(&:failed?)&.payload&.fetch(:reason)&.to_sym
+ end
end
end
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index c435048e343..37a829e3014 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -185,6 +185,7 @@ module MergeRequests
# email template itself, see `change_in_merge_request_draft_status_email` template.
notify_draft_status_changed(merge_request)
trigger_merge_request_status_updated(merge_request)
+ publish_draft_change_event(merge_request) if Feature.enabled?(:additional_merge_when_checks_ready, project)
end
if !old_title_draft && new_title_draft
@@ -196,6 +197,14 @@ module MergeRequests
end
end
+ def publish_draft_change_event(merge_request)
+ Gitlab::EventStore.publish(
+ MergeRequests::DraftStateChangeEvent.new(
+ data: { current_user_id: current_user.id, merge_request_id: merge_request.id }
+ )
+ )
+ end
+
def notify_draft_status_changed(merge_request)
notification_service.async.change_in_merge_request_draft_status(
merge_request,
diff --git a/app/services/ml/find_or_create_model_version_service.rb b/app/services/ml/find_or_create_model_version_service.rb
index 1316b2546b9..f4d3f3e72d3 100644
--- a/app/services/ml/find_or_create_model_version_service.rb
+++ b/app/services/ml/find_or_create_model_version_service.rb
@@ -11,7 +11,6 @@ module Ml
def execute
model = Ml::FindOrCreateModelService.new(project, name).execute
-
Ml::ModelVersion.find_or_create!(model, version, package)
end
diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb
index cba7398ebc0..1b852710677 100644
--- a/app/services/notes/quick_actions_service.rb
+++ b/app/services/notes/quick_actions_service.rb
@@ -61,7 +61,7 @@ module Notes
service_errors = if service_response.respond_to?(:errors)
service_response.errors.full_messages
elsif service_response.respond_to?(:[]) && service_response[:status] == :error
- service_response[:message]
+ Array.wrap(service_response[:message])
end
service_errors.blank? ? ServiceResponse.success : ServiceResponse.error(message: service_errors)
diff --git a/app/services/packages/create_dependency_service.rb b/app/services/packages/create_dependency_service.rb
index 10a86e44cb0..51f8a514c55 100644
--- a/app/services/packages/create_dependency_service.rb
+++ b/app/services/packages/create_dependency_service.rb
@@ -59,7 +59,7 @@ module Packages
# The bulk_insert statement above do not dirty the query cache. To make
# sure that the results are fresh from the database and not from a stalled
# and potentially wrong cache, this query has to be done with the query
- # chache disabled.
+ # cache disabled.
Packages::Dependency.ids_for_package_names_and_version_patterns(names_and_version_patterns)
end
end
diff --git a/app/services/packages/maven/find_or_create_package_service.rb b/app/services/packages/maven/find_or_create_package_service.rb
index ac0c77391d7..2ff3ebc3bb2 100644
--- a/app/services/packages/maven/find_or_create_package_service.rb
+++ b/app/services/packages/maven/find_or_create_package_service.rb
@@ -10,7 +10,7 @@ module Packages
package =
::Packages::Maven::PackageFinder.new(current_user, project, path: path)
- .execute
+ .execute&.last
unless Namespace::PackageSetting.duplicates_allowed?(package)
return ServiceResponse.error(message: 'Duplicate package is not allowed') if target_package_is_duplicate?(package)
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
index f6f2dbb8415..d599cecc8da 100644
--- a/app/services/packages/npm/create_package_service.rb
+++ b/app/services/packages/npm/create_package_service.rb
@@ -5,7 +5,7 @@ module Packages
include Gitlab::Utils::StrongMemoize
include ExclusiveLeaseGuard
- PACKAGE_JSON_NOT_ALLOWED_FIELDS = %w[readme readmeFilename licenseText].freeze
+ PACKAGE_JSON_NOT_ALLOWED_FIELDS = %w[readme readmeFilename licenseText contributors exports].freeze
DEFAULT_LEASE_TIMEOUT = 1.hour.to_i
def execute
diff --git a/app/services/packages/nuget/extract_metadata_file_service.rb b/app/services/packages/nuget/extract_metadata_file_service.rb
index cc040a45016..fd4f9b5d1c1 100644
--- a/app/services/packages/nuget/extract_metadata_file_service.rb
+++ b/app/services/packages/nuget/extract_metadata_file_service.rb
@@ -7,48 +7,30 @@ module Packages
MAX_FILE_SIZE = 4.megabytes.freeze
- def initialize(package_file)
- @package_file = package_file
+ def initialize(package_zip_file)
+ @package_zip_file = package_zip_file
end
def execute
- raise ExtractionError, 'invalid package file' unless valid_package_file?
-
ServiceResponse.success(payload: nuspec_file_content)
end
private
- attr_reader :package_file
-
- def valid_package_file?
- package_file &&
- package_file.package&.nuget? &&
- package_file.file.size > 0 # rubocop:disable Style/ZeroLengthPredicate
- end
+ attr_reader :package_zip_file
def nuspec_file_content
- with_zip_file do |zip_file|
- entry = zip_file.glob('*.nuspec').first
-
- raise ExtractionError, 'nuspec file not found' unless entry
- raise ExtractionError, 'nuspec file too big' if MAX_FILE_SIZE < entry.size
-
- Tempfile.open("nuget_extraction_package_file_#{package_file.id}") do |file|
- entry.extract(file.path) { true } # allow #extract to overwrite the file
- file.unlink
- file.read
- end
- rescue Zip::EntrySizeError => e
- raise ExtractionError, "nuspec file has the wrong entry size: #{e.message}"
- end
- end
+ entry = package_zip_file.glob('*.nuspec').first
+
+ raise ExtractionError, 'nuspec file not found' unless entry
+ raise ExtractionError, 'nuspec file too big' if MAX_FILE_SIZE < entry.size
- def with_zip_file
- package_file.file.use_open_file do |open_file|
- zip_file = Zip::File.new(open_file, false, true) # rubocop:disable Performance/Rubyzip
- yield(zip_file)
+ Tempfile.create('nuget_extraction_package_file') do |file|
+ entry.extract(file.path) { true } # allow #extract to overwrite the file
+ file.read
end
+ rescue Zip::EntrySizeError => e
+ raise ExtractionError, "nuspec file has the wrong entry size: #{e.message}"
end
end
end
diff --git a/app/services/packages/nuget/metadata_extraction_service.rb b/app/services/packages/nuget/metadata_extraction_service.rb
index 2c758a5ec20..53189063c85 100644
--- a/app/services/packages/nuget/metadata_extraction_service.rb
+++ b/app/services/packages/nuget/metadata_extraction_service.rb
@@ -23,10 +23,9 @@ module Packages
end
def nuspec_file_content
- ExtractMetadataFileService
+ ProcessPackageFileService
.new(package_file)
- .execute
- .payload
+ .execute[:nuspec_file_content]
end
end
end
diff --git a/app/services/packages/nuget/odata_package_entry_service.rb b/app/services/packages/nuget/odata_package_entry_service.rb
index 0cdcc38de16..679b01d6c48 100644
--- a/app/services/packages/nuget/odata_package_entry_service.rb
+++ b/app/services/packages/nuget/odata_package_entry_service.rb
@@ -5,9 +5,6 @@ module Packages
class OdataPackageEntryService
include API::Helpers::RelatedResourcesHelpers
- SEMVER_LATEST_VERSION_PLACEHOLDER = '0.0.0-latest-version'
- LATEST_VERSION_FOR_V2_DOWNLOAD_ENDPOINT = 'latest'
-
def initialize(project, params)
@project = project
@params = params
@@ -29,42 +26,40 @@ module Packages
<title type='text'>#{params[:package_name]}</title>
<content type='application/zip' src="#{download_url}"/>
<m:properties>
- <d:Version>#{package_version}</d:Version>
+ <d:Version>#{params[:package_version]}</d:Version>
</m:properties>
</entry>
XML
end
- def package_version
- params[:package_version] || SEMVER_LATEST_VERSION_PLACEHOLDER
- end
-
def id_url
expose_url "#{api_v4_projects_packages_nuget_v2_path(id: project.id)}" \
- "/Packages(Id='#{params[:package_name]}',Version='#{package_version}')"
+ "/Packages(Id='#{params[:package_name]}',Version='#{params[:package_version]}')"
end
- # TODO: use path helper when download endpoint is merged
def download_url
- expose_url "#{api_v4_projects_packages_nuget_v2_path(id: project.id)}" \
- "/download/#{params[:package_name]}/#{download_url_package_version}"
- end
-
- def download_url_package_version
- if latest_version?
- LATEST_VERSION_FOR_V2_DOWNLOAD_ENDPOINT
+ if params[:package_version].present?
+ expose_url api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
+ {
+ id: project.id,
+ package_name: params[:package_name],
+ package_version: params[:package_version],
+ package_filename: file_name
+ },
+ true
+ )
else
- params[:package_version]
+ xml_base
end
end
- def latest_version?
- params[:package_version].nil? || params[:package_version] == SEMVER_LATEST_VERSION_PLACEHOLDER
- end
-
def xml_base
expose_url api_v4_projects_packages_nuget_v2_path(id: project.id)
end
+
+ def file_name
+ "#{params[:package_name]}.#{params[:package_version]}.nupkg"
+ end
end
end
end
diff --git a/app/services/packages/nuget/process_package_file_service.rb b/app/services/packages/nuget/process_package_file_service.rb
new file mode 100644
index 00000000000..fa7a84ee3d6
--- /dev/null
+++ b/app/services/packages/nuget/process_package_file_service.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class ProcessPackageFileService
+ ExtractionError = Class.new(StandardError)
+ NUGET_SYMBOL_FILE_EXTENSION = '.snupkg'
+
+ def initialize(package_file)
+ @package_file = package_file
+ end
+
+ def execute
+ raise ExtractionError, 'invalid package file' unless valid_package_file?
+
+ nuspec_content = nil
+
+ with_zip_file do |zip_file|
+ nuspec_content = nuspec_file_content(zip_file)
+ create_symbol_files(zip_file) if symbol_package_file?
+ end
+
+ ServiceResponse.success(payload: { nuspec_file_content: nuspec_content })
+ end
+
+ private
+
+ attr_reader :package_file
+
+ def valid_package_file?
+ package_file &&
+ package_file.package&.nuget? &&
+ package_file.file.size > 0 # rubocop:disable Style/ZeroLengthPredicate
+ end
+
+ def with_zip_file(&block)
+ package_file.file.use_open_file(unlink_early: false) do |open_file|
+ Zip::File.open(open_file.file_path, &block) # rubocop: disable Performance/Rubyzip
+ end
+ end
+
+ def nuspec_file_content(zip_file)
+ ::Packages::Nuget::ExtractMetadataFileService
+ .new(zip_file)
+ .execute
+ .payload
+ end
+
+ def create_symbol_files(zip_file)
+ ::Packages::Nuget::Symbols::CreateSymbolFilesService
+ .new(package_file.package, zip_file)
+ .execute
+ end
+
+ def symbol_package_file?
+ package_file.file_name.end_with?(NUGET_SYMBOL_FILE_EXTENSION)
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/symbols/create_symbol_files_service.rb b/app/services/packages/nuget/symbols/create_symbol_files_service.rb
new file mode 100644
index 00000000000..03e14ba00e1
--- /dev/null
+++ b/app/services/packages/nuget/symbols/create_symbol_files_service.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ module Symbols
+ class CreateSymbolFilesService
+ ExtractionError = Class.new(StandardError)
+ SYMBOL_ENTRIES_LIMIT = 100
+ CONTENT_TYPE = 'application/octet-stream'
+
+ def initialize(package, package_zip_file)
+ @package = package
+ @symbol_entries = package_zip_file.glob('**/*.pdb')
+ end
+
+ def execute
+ return if symbol_entries.empty?
+
+ process_symbol_entries
+ rescue ExtractionError => e
+ Gitlab::ErrorTracking.log_exception(e, class: self.class.name, package_id: package.id)
+ end
+
+ private
+
+ attr_reader :package, :symbol_entries
+
+ def process_symbol_entries
+ Tempfile.create('nuget_extraction_symbol_file') do |tmp_file|
+ symbol_entries.each_with_index do |entry, index|
+ raise ExtractionError, 'too many symbol entries' if index >= SYMBOL_ENTRIES_LIMIT
+
+ entry.extract(tmp_file.path) { true }
+ File.open(tmp_file.path) do |file|
+ create_symbol(entry.name, file)
+ end
+ end
+ end
+ rescue Zip::EntrySizeError => e
+ raise ExtractionError, "symbol file has the wrong entry size: #{e.message}"
+ rescue Zip::EntryNameError => e
+ raise ExtractionError, "symbol file has the wrong entry name: #{e.message}"
+ end
+
+ def create_symbol(path, file)
+ signature = extract_signature(file.read(1.kilobyte))
+ return if signature.blank?
+
+ ::Packages::Nuget::Symbol.create!(
+ package: package,
+ file: { tempfile: file, filename: path.downcase, content_type: CONTENT_TYPE },
+ file_path: path,
+ signature: signature,
+ size: file.size
+ )
+ rescue StandardError => e
+ Gitlab::ErrorTracking.log_exception(e, class: self.class.name, package_id: package.id)
+ end
+
+ def extract_signature(content_fragment)
+ ExtractSymbolSignatureService
+ .new(content_fragment)
+ .execute
+ .payload
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/symbols/extract_symbol_signature_service.rb b/app/services/packages/nuget/symbols/extract_symbol_signature_service.rb
new file mode 100644
index 00000000000..c2ccdb517b5
--- /dev/null
+++ b/app/services/packages/nuget/symbols/extract_symbol_signature_service.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ module Symbols
+ class ExtractSymbolSignatureService
+ include Gitlab::Utils::StrongMemoize
+
+ # More information about the GUID format can be found here:
+ # https://github.com/dotnet/symstore/blob/main/docs/specs/SSQP_Key_Conventions.md#key-formatting-basic-rules
+ GUID_START_INDEX = 7
+ GUID_END_INDEX = 22
+ GUID_PARTS_LENGTHS = [4, 2, 2, 8].freeze
+ GUID_AGE_PART = 'FFFFFFFF'
+ TWO_CHARACTER_HEX_REGEX = /\h{2}/
+
+ # The extraction of the signature in this service is based on the following documentation:
+ # https://github.com/dotnet/symstore/blob/main/docs/specs/SSQP_Key_Conventions.md#portable-pdb-signature
+
+ def initialize(symbol_content)
+ @symbol_content = symbol_content
+ end
+
+ def execute
+ return error_response unless signature
+
+ ServiceResponse.success(payload: signature)
+ end
+
+ private
+
+ attr_reader :symbol_content
+
+ def signature
+ # Find the index of the first occurrence of 'Blob'
+ guid_index = symbol_content.index('Blob')
+ return if guid_index.nil?
+
+ # Extract the binary GUID from the symbol content
+ guid = symbol_content[(guid_index + GUID_START_INDEX)..(guid_index + GUID_END_INDEX)]
+ return if guid.nil?
+
+ # Convert the GUID into an array of two-character hex strings
+ guid = guid.unpack('H*').flat_map { |el| el.scan(TWO_CHARACTER_HEX_REGEX) }
+
+ # Reorder the GUID parts based on arbitrary lengths
+ guid = GUID_PARTS_LENGTHS.map { |length| guid.shift(length) }
+
+ # Concatenate the parts of the GUID back together
+ result = guid.first(3).map(&:reverse)
+ result << guid.last
+ result = result.join
+ result << GUID_AGE_PART
+ end
+ strong_memoize_attr :signature
+
+ def error_response
+ ServiceResponse.error(message: 'Could not find the signature in the symbol file')
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/update_package_from_metadata_service.rb b/app/services/packages/nuget/update_package_from_metadata_service.rb
index 258f8c8f6aa..4cec4ed2fae 100644
--- a/app/services/packages/nuget/update_package_from_metadata_service.rb
+++ b/app/services/packages/nuget/update_package_from_metadata_service.rb
@@ -54,13 +54,16 @@ module Packages
update_linked_package
end
- update_package(target_package)
+ build_infos = package_to_destroy&.build_infos || []
+
+ update_package(target_package, build_infos)
+ update_symbol_files(target_package, package_to_destroy) if symbol_package?
::Packages::UpdatePackageFileService.new(@package_file, package_id: target_package.id, file_name: package_filename)
.execute
package_to_destroy&.destroy!
end
- def update_package(package)
+ def update_package(package, build_infos)
return if symbol_package?
::Packages::Nuget::SyncMetadatumService
@@ -71,28 +74,21 @@ module Packages
.new(package, package_tags)
.execute
+ package.build_infos << build_infos if build_infos.any?
rescue StandardError => e
raise InvalidMetadataError, e.message
end
+ def update_symbol_files(package, package_to_destroy)
+ package_to_destroy.nuget_symbols.update_all(package_id: package.id)
+ end
+
def valid_metadata?
fields = [package_name, package_version, package_description]
fields << package_authors unless symbol_package?
fields.all?(&:present?)
end
- def link_to_existing_package
- package_to_destroy = @package_file.package
- # Updating package_id updates the path where the file is stored.
- # We must pass the file again so that CarrierWave can handle the update
- @package_file.update!(
- package_id: existing_package.id,
- file: @package_file.file
- )
- package_to_destroy.destroy!
- existing_package
- end
-
def update_linked_package
@package_file.package.update!(
name: package_name,
@@ -106,12 +102,15 @@ module Packages
end
def existing_package
- @package_file.project.packages
- .nuget
- .with_name(package_name)
- .with_version(package_version)
- .not_pending_destruction
- .first
+ ::Packages::Nuget::PackageFinder
+ .new(
+ nil,
+ @package_file.project,
+ package_name: package_name,
+ package_version: package_version
+ )
+ .execute
+ .first
end
strong_memoize_attr :existing_package
diff --git a/app/services/packages/protection/create_rule_service.rb b/app/services/packages/protection/create_rule_service.rb
new file mode 100644
index 00000000000..e69eb8faf60
--- /dev/null
+++ b/app/services/packages/protection/create_rule_service.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Packages
+ module Protection
+ class CreateRuleService < BaseProjectService
+ ALLOWED_ATTRIBUTES = %i[
+ package_name_pattern
+ package_type
+ push_protected_up_to_access_level
+ ].freeze
+
+ def execute
+ unless can?(current_user, :admin_package, project)
+ error_message = _('Unauthorized to create a package protection rule')
+ return service_response_error(message: error_message)
+ end
+
+ package_protection_rule = project.package_protection_rules.create(params.slice(*ALLOWED_ATTRIBUTES))
+
+ unless package_protection_rule.persisted?
+ return service_response_error(message: package_protection_rule.errors.full_messages)
+ end
+
+ ServiceResponse.success(payload: { package_protection_rule: package_protection_rule })
+ rescue StandardError => e
+ service_response_error(message: e.message)
+ end
+
+ private
+
+ def service_response_error(message:)
+ ServiceResponse.error(
+ message: message,
+ payload: { package_protection_rule: nil }
+ )
+ end
+ end
+ end
+end
diff --git a/app/services/pages/migrate_from_legacy_storage_service.rb b/app/services/pages/migrate_from_legacy_storage_service.rb
deleted file mode 100644
index d102f93e863..00000000000
--- a/app/services/pages/migrate_from_legacy_storage_service.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-# frozen_string_literal: true
-
-module Pages
- class MigrateFromLegacyStorageService
- def initialize(logger, ignore_invalid_entries:, mark_projects_as_not_deployed:)
- @logger = logger
- @ignore_invalid_entries = ignore_invalid_entries
- @mark_projects_as_not_deployed = mark_projects_as_not_deployed
-
- @migrated = 0
- @errored = 0
- @counters_lock = Mutex.new
- end
-
- def execute_with_threads(threads:, batch_size:)
- @queue = SizedQueue.new(1)
-
- migration_threads = start_migration_threads(threads)
-
- ProjectPagesMetadatum.only_on_legacy_storage.each_batch(of: batch_size) do |batch|
- @queue.push(batch)
- end
-
- @queue.close
-
- @logger.info(message: "Pages legacy storage migration: Waiting for threads to finish...")
- migration_threads.each(&:join)
-
- { migrated: @migrated, errored: @errored }
- end
-
- def execute_for_batch(project_ids)
- batch = ProjectPagesMetadatum.only_on_legacy_storage.where(project_id: project_ids) # rubocop: disable CodeReuse/ActiveRecord
-
- process_batch(batch)
-
- { migrated: @migrated, errored: @errored }
- end
-
- private
-
- def start_migration_threads(count)
- Array.new(count) do
- Thread.new do
- while batch = @queue.pop
- Rails.application.executor.wrap do
- process_batch(batch)
- end
- end
- end
- end
- end
-
- def process_batch(batch)
- batch.with_project_route_and_deployment.each do |metadatum|
- project = metadatum.project
-
- migrate_project(project)
- end
-
- @logger.info(message: "Pages legacy storage migration: batch processed", migrated: @migrated, errored: @errored)
- rescue StandardError => e
- # This method should never raise exception otherwise all threads might be killed
- # and this will result in queue starving (and deadlock)
- Gitlab::ErrorTracking.track_exception(e)
- @logger.error(message: "Pages legacy storage migration: failed processing a batch: #{e.message}")
- end
-
- def migrate_project(project)
- result = nil
- time = Benchmark.realtime do
- result = ::Pages::MigrateLegacyStorageToDeploymentService.new(project,
- ignore_invalid_entries: @ignore_invalid_entries,
- mark_projects_as_not_deployed: @mark_projects_as_not_deployed).execute
- end
-
- if result[:status] == :success
- @logger.info(message: "Pages legacy storage migration: project migrated: #{result[:message]}", project_id: project.id, pages_path: project.pages_path, duration: time.round(2))
- @counters_lock.synchronize { @migrated += 1 }
- else
- @logger.error(message: "Pages legacy storage migration: project failed to be migrated: #{result[:message]}", project_id: project.id, pages_path: project.pages_path, duration: time.round(2))
- @counters_lock.synchronize { @errored += 1 }
- end
- rescue StandardError => e
- @counters_lock.synchronize { @errored += 1 }
- @logger.error(message: "Pages legacy storage migration: project failed to be migrated: #{result[:message]}", project_id: project&.id, pages_path: project&.pages_path)
- Gitlab::ErrorTracking.track_exception(e, project_id: project&.id)
- end
- end
-end
diff --git a/app/services/pages/migrate_legacy_storage_to_deployment_service.rb b/app/services/pages/migrate_legacy_storage_to_deployment_service.rb
deleted file mode 100644
index 9c1671fbc15..00000000000
--- a/app/services/pages/migrate_legacy_storage_to_deployment_service.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-module Pages
- class MigrateLegacyStorageToDeploymentService
- include BaseServiceUtility
-
- attr_reader :project
-
- def initialize(project, ignore_invalid_entries: false, mark_projects_as_not_deployed: false)
- @project = project
- @ignore_invalid_entries = ignore_invalid_entries
- @mark_projects_as_not_deployed = mark_projects_as_not_deployed
- end
-
- def execute
- zip_result = ::Pages::ZipDirectoryService.new(project.pages_path, ignore_invalid_entries: @ignore_invalid_entries).execute
-
- if zip_result[:status] == :error
- return error("Can't create zip archive: #{zip_result[:message]}")
- end
-
- archive_path = zip_result[:archive_path]
-
- unless archive_path
- return error("Archive not created. Missing public directory in #{@project.pages_path}") unless @mark_projects_as_not_deployed
-
- project.set_first_pages_deployment!(nil)
-
- return success(
- message: "Archive not created. Missing public directory in #{project.pages_path}? Marked project as not deployed")
- end
-
- deployment = nil
- File.open(archive_path) do |file|
- deployment = project.pages_deployments.create!(
- file: file,
- file_count: zip_result[:entries_count],
- file_sha256: Digest::SHA256.file(archive_path).hexdigest
- )
- end
-
- project.set_first_pages_deployment!(deployment)
-
- success
- ensure
- FileUtils.rm_f(archive_path) if archive_path
- end
- end
-end
diff --git a/app/services/pages/zip_directory_service.rb b/app/services/pages/zip_directory_service.rb
deleted file mode 100644
index c9029b9666a..00000000000
--- a/app/services/pages/zip_directory_service.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-# frozen_string_literal: true
-
-module Pages
- class ZipDirectoryService
- include BaseServiceUtility
- include Gitlab::Utils::StrongMemoize
-
- # used only to track exceptions in Sentry
- InvalidEntryError = Class.new(StandardError)
-
- PUBLIC_DIR = 'public'
-
- attr_reader :public_dir, :real_dir
-
- def initialize(input_dir, ignore_invalid_entries: false)
- @input_dir = input_dir
- @ignore_invalid_entries = ignore_invalid_entries
- end
-
- def execute
- return success unless resolve_public_dir
-
- output_file = File.join(real_dir, "@migrated.zip") # '@' to avoid any name collision with groups or projects
-
- FileUtils.rm_f(output_file)
-
- entries_count = 0
- # Since we're writing not reading here, we can safely silence the cop.
- # It currently cannot discern between opening for reading or writing.
- ::Zip::File.open(output_file, ::Zip::File::CREATE) do |zipfile| # rubocop:disable Performance/Rubyzip
- write_entry(zipfile, PUBLIC_DIR)
- entries_count = zipfile.entries.count
- end
-
- success(archive_path: output_file, entries_count: entries_count)
- rescue StandardError => e
- FileUtils.rm_f(output_file) if output_file
- raise e
- end
-
- private
-
- def resolve_public_dir
- @real_dir = File.realpath(@input_dir)
- @public_dir = File.join(real_dir, PUBLIC_DIR)
-
- valid_path?(public_dir)
- rescue Errno::ENOENT
- false
- end
-
- def write_entry(zipfile, zipfile_path)
- disk_file_path = File.join(real_dir, zipfile_path)
-
- unless valid_path?(disk_file_path)
- # archive with invalid entry will just have this entry missing
- raise InvalidEntryError, "#{disk_file_path} is invalid, input_dir: #{@input_dir}"
- end
-
- ftype = File.lstat(disk_file_path).ftype
- case ftype
- when 'directory'
- recursively_zip_directory(zipfile, disk_file_path, zipfile_path)
- when 'file', 'link'
- zipfile.add(zipfile_path, disk_file_path)
- else
- raise InvalidEntryError, "#{disk_file_path} has invalid ftype: #{ftype}, input_dir: #{@input_dir}"
- end
- rescue Errno::ENOENT, Errno::ELOOP, InvalidEntryError => e
- Gitlab::ErrorTracking.track_exception(e, input_dir: @input_dir, disk_file_path: disk_file_path)
-
- raise e unless @ignore_invalid_entries
- end
-
- def recursively_zip_directory(zipfile, disk_file_path, zipfile_path)
- zipfile.mkdir(zipfile_path)
-
- entries = Dir.entries(disk_file_path) - %w[. ..]
- entries = entries.map { |entry| File.join(zipfile_path, entry) }
-
- write_entries(zipfile, entries)
- end
-
- def write_entries(zipfile, entries)
- entries.each do |zipfile_path|
- write_entry(zipfile, zipfile_path)
- end
- end
-
- # SafeZip was introduced only recently,
- # so we have invalid entries on disk
- def valid_path?(disk_file_path)
- realpath = File.realpath(disk_file_path)
- realpath == public_dir || realpath.start_with?(public_dir + "/")
- end
- end
-end
diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb
index 9dc957b5be2..93d68eec3bc 100644
--- a/app/services/projects/after_rename_service.rb
+++ b/app/services/projects/after_rename_service.rb
@@ -62,13 +62,9 @@ module Projects
def rename_or_migrate_repository!
success =
- if migrate_to_hashed_storage?
- ::Projects::HashedStorage::MigrationService
- .new(project, full_path_before)
- .execute
- else
- project.storage.rename_repo(old_full_path: full_path_before, new_full_path: full_path_after)
- end
+ ::Projects::HashedStorage::MigrationService
+ .new(project, full_path_before)
+ .execute
rename_failed! unless success
end
@@ -105,11 +101,6 @@ module Projects
)
end
- def migrate_to_hashed_storage?
- Gitlab::CurrentSettings.hashed_storage_enabled? &&
- project.storage_upgradable?
- end
-
def send_move_instructions?
!project.import_started?
end
@@ -147,5 +138,3 @@ module Projects
end
end
end
-
-Projects::AfterRenameService.prepend_mod_with('Projects::AfterRenameService')
diff --git a/app/services/projects/container_repository/cleanup_tags_base_service.rb b/app/services/projects/container_repository/cleanup_tags_base_service.rb
index 61b09de1643..45557d03502 100644
--- a/app/services/projects/container_repository/cleanup_tags_base_service.rb
+++ b/app/services/projects/container_repository/cleanup_tags_base_service.rb
@@ -100,7 +100,7 @@ module Projects
def older_than_in_seconds
strong_memoize(:older_than_in_seconds) do
- ChronicDuration.parse(older_than, use_complete_matcher: true).seconds
+ ChronicDuration.parse(older_than).seconds
end
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index e4987438c57..e0ee3683ac8 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -229,7 +229,7 @@ module Projects
%w[routes redirect_routes], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424281'
) do
ApplicationRecord.transaction do
- @project.create_or_update_import_data(data: @import_data[:data], credentials: @import_data[:credentials]) if @import_data
+ @project.build_or_assign_import_data(data: @import_data[:data], credentials: @import_data[:credentials]) if @import_data
# Avoid project callbacks being triggered multiple times by saving the parent first.
# See https://github.com/rails/rails/issues/41701.
diff --git a/app/services/projects/group_links/create_service.rb b/app/services/projects/group_links/create_service.rb
index f77bae71d63..c9642fb495a 100644
--- a/app/services/projects/group_links/create_service.rb
+++ b/app/services/projects/group_links/create_service.rb
@@ -16,7 +16,7 @@ module Projects
delegate :root_ancestor, to: :project
def valid_to_create?
- can?(current_user, :read_namespace, shared_with_group) && sharing_allowed?
+ can?(current_user, :read_namespace_via_membership, shared_with_group) && sharing_allowed?
end
def build_link
diff --git a/app/services/projects/hashed_storage/base_repository_service.rb b/app/services/projects/hashed_storage/base_repository_service.rb
deleted file mode 100644
index 6241a3e144f..00000000000
--- a/app/services/projects/hashed_storage/base_repository_service.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module HashedStorage
- # Returned when repository can't be made read-only because there is already a git transfer in progress
- RepositoryInUseError = Class.new(StandardError)
-
- class BaseRepositoryService < BaseService
- include Gitlab::ShellAdapter
-
- attr_reader :old_disk_path, :new_disk_path, :old_storage_version,
- :logger, :move_wiki, :move_design
-
- def initialize(project:, old_disk_path:, logger: nil)
- @project = project
- @logger = logger || Gitlab::AppLogger
- @old_disk_path = old_disk_path
- @move_wiki = has_wiki?
- @move_design = has_design?
- end
-
- protected
-
- def has_wiki?
- gitlab_shell.repository_exists?(project.repository_storage, "#{old_wiki_disk_path}.git")
- end
-
- def has_design?
- gitlab_shell.repository_exists?(project.repository_storage, "#{old_design_disk_path}.git")
- end
-
- def move_repository(from_name, to_name)
- from_exists = gitlab_shell.repository_exists?(project.repository_storage, "#{from_name}.git")
- to_exists = gitlab_shell.repository_exists?(project.repository_storage, "#{to_name}.git")
-
- # If we don't find the repository on either original or target we should log that as it could be an issue if the
- # project was not originally empty.
- if !from_exists && !to_exists
- logger.warn "Can't find a repository on either source or target paths for #{project.full_path} (ID=#{project.id}) ..."
-
- # We return true so we still reflect the change in the database.
- # Next time the repository is (re)created it will be under the new storage layout
- return true
- elsif !from_exists
- # Repository have been moved already.
- return true
- end
-
- gitlab_shell.mv_repository(project.repository_storage, from_name, to_name).tap do |moved|
- if moved
- logger.info("Repository moved from '#{from_name}' to '#{to_name}' (PROJECT_ID=#{project.id})")
- else
- logger.error("Repository cannot be moved from '#{from_name}' to '#{to_name}' (PROJECT_ID=#{project.id})")
- end
- end
- end
-
- def move_repositories
- result = move_repository(old_disk_path, new_disk_path)
- project.reload_repository!
-
- if move_wiki
- result &&= move_repository(old_wiki_disk_path, new_wiki_disk_path)
- project.clear_memoization(:wiki)
- end
-
- if move_design
- result &&= move_repository(old_design_disk_path, new_design_disk_path)
- project.clear_memoization(:design_repository)
- end
-
- result
- end
-
- def rollback_folder_move
- move_repository(new_disk_path, old_disk_path)
- move_repository(new_wiki_disk_path, old_wiki_disk_path)
- move_repository(new_design_disk_path, old_design_disk_path) if move_design
- end
-
- def try_to_set_repository_read_only!
- project.set_repository_read_only!
- rescue Project::RepositoryReadOnlyError => err
- migration_error = "Target repository '#{old_disk_path}' cannot be made read-only: #{err.message}"
- logger.error migration_error
-
- raise RepositoryInUseError, migration_error
- end
-
- def wiki_path_suffix
- @wiki_path_suffix ||= Gitlab::GlRepository::WIKI.path_suffix
- end
-
- def old_wiki_disk_path
- @old_wiki_disk_path ||= "#{old_disk_path}#{wiki_path_suffix}"
- end
-
- def new_wiki_disk_path
- @new_wiki_disk_path ||= "#{new_disk_path}#{wiki_path_suffix}"
- end
-
- def design_path_suffix
- @design_path_suffix ||= ::Gitlab::GlRepository::DESIGN.path_suffix
- end
-
- def old_design_disk_path
- @old_design_disk_path ||= "#{old_disk_path}#{design_path_suffix}"
- end
-
- def new_design_disk_path
- @new_design_disk_path ||= "#{new_disk_path}#{design_path_suffix}"
- end
- end
- end
-end
diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb
deleted file mode 100644
index b65d0e63fd3..00000000000
--- a/app/services/projects/hashed_storage/migrate_repository_service.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module HashedStorage
- class MigrateRepositoryService < BaseRepositoryService
- def execute
- try_to_set_repository_read_only!
-
- @old_storage_version = project.storage_version
- project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
-
- @new_disk_path = project.disk_path
-
- result = move_repositories
-
- if result
- project.set_full_path
- project.track_project_repository
- else
- rollback_folder_move
- project.storage_version = nil
- end
-
- project.transaction do
- project.save!(validate: false)
- project.set_repository_writable!
- end
-
- result
- rescue Gitlab::Git::CommandError => e
- logger.error("Repository #{project.full_path} failed to upgrade (PROJECT_ID=#{project.id}). Git operation failed: #{e.inspect}")
-
- rollback_migration!
-
- false
- rescue OpenSSL::Cipher::CipherError => e
- logger.error("Repository #{project.full_path} failed to upgrade (PROJECT_ID=#{project.id}). There is a problem with encrypted attributes: #{e.inspect}")
-
- rollback_migration!
-
- false
- end
-
- private
-
- def rollback_migration!
- rollback_folder_move
- project.storage_version = nil
- project.set_repository_writable!
- end
- end
- end
-end
-
-Projects::HashedStorage::MigrateRepositoryService.prepend_mod_with('Projects::HashedStorage::MigrateRepositoryService')
diff --git a/app/services/projects/hashed_storage/migration_service.rb b/app/services/projects/hashed_storage/migration_service.rb
index 57a775a8f9e..e2015a4cca6 100644
--- a/app/services/projects/hashed_storage/migration_service.rb
+++ b/app/services/projects/hashed_storage/migration_service.rb
@@ -12,11 +12,6 @@ module Projects
end
def execute
- # Migrate repository from Legacy to Hashed Storage
- unless project.hashed_storage?(:repository)
- return false unless migrate_repository_service.execute
- end
-
# Migrate attachments from Legacy to Hashed Storage
unless project.hashed_storage?(:attachments)
return false unless migrate_attachments_service.execute
@@ -27,10 +22,6 @@ module Projects
private
- def migrate_repository_service
- HashedStorage::MigrateRepositoryService.new(project: project, old_disk_path: old_disk_path, logger: logger)
- end
-
def migrate_attachments_service
HashedStorage::MigrateAttachmentsService.new(project: project, old_disk_path: old_disk_path, logger: logger)
end
diff --git a/app/services/projects/hashed_storage/rollback_attachments_service.rb b/app/services/projects/hashed_storage/rollback_attachments_service.rb
deleted file mode 100644
index 4bb8cb605a3..00000000000
--- a/app/services/projects/hashed_storage/rollback_attachments_service.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module HashedStorage
- class RollbackAttachmentsService < BaseAttachmentService
- def execute
- origin = FileUploader.absolute_base_dir(project)
-
- project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
- target = FileUploader.absolute_base_dir(project)
-
- @new_disk_path = FileUploader.base_dir(project)
-
- result = move_folder!(origin, target)
-
- if result
- project.save!(validate: false)
-
- yield if block_given?
- else
- # Rollback changes
- project.rollback!
- end
-
- result
- end
- end
- end
-end
diff --git a/app/services/projects/hashed_storage/rollback_repository_service.rb b/app/services/projects/hashed_storage/rollback_repository_service.rb
deleted file mode 100644
index f4146ff9158..00000000000
--- a/app/services/projects/hashed_storage/rollback_repository_service.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module HashedStorage
- class RollbackRepositoryService < BaseRepositoryService
- def execute
- try_to_set_repository_read_only!
-
- @old_storage_version = project.storage_version
- project.storage_version = nil
-
- @new_disk_path = project.disk_path
-
- result = move_repositories
-
- if result
- project.set_full_path
- project.track_project_repository
- else
- rollback_folder_move
- project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
- end
-
- project.transaction do
- project.save!(validate: false)
- project.set_repository_writable!
- end
-
- result
- rescue Gitlab::Git::CommandError => e
- logger.error("Repository #{project.full_path} failed to rollback (PROJECT_ID=#{project.id}). Git operation failed: #{e.inspect}")
-
- rollback_migration!
-
- false
- rescue OpenSSL::Cipher::CipherError => e
- logger.error("Repository #{project.full_path} failed to rollback (PROJECT_ID=#{project.id}). There is a problem with encrypted attributes: #{e.inspect}")
-
- rollback_migration!
-
- false
- end
-
- private
-
- def rollback_migration!
- rollback_folder_move
- project.storage_version = ::Project::HASHED_STORAGE_FEATURES[:repository]
- project.set_repository_writable!
- end
- end
- end
-end
diff --git a/app/services/projects/hashed_storage/rollback_service.rb b/app/services/projects/hashed_storage/rollback_service.rb
deleted file mode 100644
index 01b343a12d1..00000000000
--- a/app/services/projects/hashed_storage/rollback_service.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module HashedStorage
- class RollbackService < BaseService
- attr_reader :logger, :old_disk_path
-
- def initialize(project, old_disk_path, logger: nil)
- @project = project
- @old_disk_path = old_disk_path
- @logger = logger || Gitlab::AppLogger
- end
-
- def execute
- # Rollback attachments from Hashed Storage to Legacy
- if project.hashed_storage?(:attachments)
- return false unless rollback_attachments_service.execute
- end
-
- # Rollback repository from Hashed Storage to Legacy
- if project.hashed_storage?(:repository)
- rollback_repository_service.execute
- end
- end
-
- private
-
- def rollback_attachments_service
- HashedStorage::RollbackAttachmentsService.new(project: project, old_disk_path: old_disk_path, logger: logger)
- end
-
- def rollback_repository_service
- HashedStorage::RollbackRepositoryService.new(project: project, old_disk_path: old_disk_path, logger: logger)
- end
- end
- end
-end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index e22b728cea3..fde56d8429e 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -29,7 +29,7 @@ module Projects
after_execute_hook
success
- rescue Gitlab::UrlBlocker::BlockedUrlError, StandardError => e
+ rescue Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError, StandardError => e
Gitlab::Import::ImportFailureService.track(
project_id: project.id,
error_source: self.class.name,
@@ -76,7 +76,7 @@ module Projects
if project.external_import? && !unknown_url?
begin
@resolved_address = get_resolved_address
- rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ rescue Gitlab::HTTP_V2::UrlBlocker::BlockedUrlError => e
raise e, s_("ImportProjects|Blocked import URL: %{message}") % { message: e.message }
end
end
diff --git a/app/services/projects/in_product_marketing_campaign_emails_service.rb b/app/services/projects/in_product_marketing_campaign_emails_service.rb
deleted file mode 100644
index a549d8f594e..00000000000
--- a/app/services/projects/in_product_marketing_campaign_emails_service.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- class InProductMarketingCampaignEmailsService
- include Gitlab::Experiment::Dsl
-
- def initialize(project, campaign)
- @project = project
- @campaign = campaign
- @sent_email_records = ::Users::InProductMarketingEmailRecords.new
- end
-
- def execute
- send_emails
- end
-
- private
-
- attr_reader :project, :campaign, :sent_email_records
-
- def send_emails
- project_users.each do |user|
- send_email(user)
- end
-
- sent_email_records.save!
- end
-
- def project_users
- @project_users ||= project.users.merge(Users::InProductMarketingEmail.without_campaign(campaign))
- end
-
- def project_users_max_access_levels
- ids = project_users.map(&:id)
- @project_users_max_access_levels ||= project.team.max_member_access_for_user_ids(ids)
- end
-
- def send_email(user)
- return unless user.can?(:receive_notifications)
- return unless target_user?(user)
-
- Notify.build_ios_app_guide_email(user.notification_email_or_default).deliver_later
-
- sent_email_records.add(user, campaign: campaign)
- experiment(:build_ios_app_guide_email, project: project).track(:email_sent)
- end
-
- def target_user?(user)
- max_access_level = project_users_max_access_levels[user.id]
- max_access_level >= Gitlab::Access::DEVELOPER
- end
- end
-end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 458eaec4e2e..fe19d1f051d 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -11,8 +11,8 @@ module Projects
noteable_owner +
participants_in_noteable +
all_members +
- groups +
- project_members
+ project_members +
+ groups
render_participants_as_hash(participants.uniq)
end
diff --git a/app/services/projects/record_target_platforms_service.rb b/app/services/projects/record_target_platforms_service.rb
index 664e72e9785..d43b587154b 100644
--- a/app/services/projects/record_target_platforms_service.rb
+++ b/app/services/projects/record_target_platforms_service.rb
@@ -28,26 +28,11 @@ module Projects
project_setting.target_platforms = target_platforms
project_setting.save
-
- send_build_ios_app_guide_email
-
project_setting.target_platforms
end
def project_setting
@project_setting ||= ::ProjectSetting.find_or_initialize_by(project: project) # rubocop:disable CodeReuse/ActiveRecord
end
-
- def experiment_candidate?
- experiment(:build_ios_app_guide_email, project: project).run
- end
-
- def send_build_ios_app_guide_email
- return unless target_platforms.include? :ios
- return unless experiment_candidate?
-
- campaign = Users::InProductMarketingEmail::BUILD_IOS_APP_GUIDE
- Projects::InProductMarketingCampaignEmailsService.new(project, campaign).execute
- end
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 3d08039942b..30d9e1922cc 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -124,9 +124,6 @@ module Projects
# Notifications
project.send_move_instructions(@old_path)
- # Directories on disk
- move_project_folders(project)
-
transfer_missing_group_resources(@old_group)
# Move uploads
@@ -235,44 +232,15 @@ module Projects
end
def rollback_side_effects
- rollback_folder_move
project.reset
update_namespace_and_visibility(@old_namespace)
update_repository_configuration(@old_path)
end
- def rollback_folder_move
- return if project.hashed_storage?(:repository)
-
- move_repo_folder(@new_path, @old_path)
- move_repo_folder(new_wiki_repo_path, old_wiki_repo_path)
- move_repo_folder(new_design_repo_path, old_design_repo_path)
- end
-
- def move_repo_folder(from_name, to_name)
- gitlab_shell.mv_repository(project.repository_storage, from_name, to_name)
- end
-
def execute_system_hooks
system_hook_service.execute_hooks_for(project, :transfer)
end
- def move_project_folders(project)
- return if project.hashed_storage?(:repository)
-
- # Move main repository
- unless move_repo_folder(@old_path, @new_path)
- raise TransferError, s_("TransferProject|Cannot move project")
- end
-
- # Disk path is changed; we need to ensure we reload it
- project.reload_repository!
-
- # Move wiki and design repos also if present
- move_repo_folder(old_wiki_repo_path, new_wiki_repo_path)
- move_repo_folder(old_design_repo_path, new_design_repo_path)
- end
-
def move_project_uploads(project)
return if project.hashed_storage?(:attachments)
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index dc92c501b8c..ab38efff7c9 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -109,11 +109,6 @@ module Projects
PagesDeployment.deactivate_deployments_older_than(
deployment,
time: OLD_DEPLOYMENTS_DESTRUCTION_DELAY.from_now)
-
- DestroyPagesDeploymentsWorker.perform_in(
- OLD_DEPLOYMENTS_DESTRUCTION_DELAY,
- project.id,
- deployment.id)
end
def register_attempt
diff --git a/app/services/projects/update_repository_storage_service.rb b/app/services/projects/update_repository_storage_service.rb
index 799ae5677c3..85fb1890fcd 100644
--- a/app/services/projects/update_repository_storage_service.rb
+++ b/app/services/projects/update_repository_storage_service.rb
@@ -48,13 +48,8 @@ module Projects
pool_repository: pool_repository
)
- checksum, new_checksum = replicate_object_pool_repository(from: pool_repository, to: target_pool_repository)
-
- if checksum != new_checksum
- raise Error,
- format(s_('UpdateRepositoryStorage|Failed to verify %{type} repository checksum from %{old} to %{new}'),
- type: 'object_pool', old: checksum, new: new_checksum)
- end
+ Repositories::ReplicateService.new(pool_repository.object_pool.repository)
+ .execute(target_pool_repository.object_pool.repository, :object_pool)
end
def remove_old_paths
@@ -96,19 +91,6 @@ module Projects
)
end
- def replicate_object_pool_repository(from:, to:)
- old_object_pool = from.object_pool
- new_object_pool = to.object_pool
-
- checksum = old_object_pool.repository.checksum
-
- new_object_pool.repository.replicate(old_object_pool.repository)
-
- new_checksum = new_object_pool.repository.checksum
-
- [checksum, new_checksum]
- end
-
def replicate_object_pool_on_move_ff_enabled?
Feature.enabled?(:replicate_object_pool_on_move, project)
end
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index f0243d844d9..95e0861a37a 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -19,7 +19,7 @@ module Releases
return tag unless tag.is_a?(Gitlab::Git::Tag)
if project.catalog_resource
- response = Ci::Catalog::ValidateResourceService.new(project, ref).execute
+ response = Ci::Catalog::Resources::ValidateService.new(project, ref).execute
return error(response.message) if response.error?
end
diff --git a/app/services/releases/destroy_service.rb b/app/services/releases/destroy_service.rb
index 41b421662ef..78613c05ff1 100644
--- a/app/services/releases/destroy_service.rb
+++ b/app/services/releases/destroy_service.rb
@@ -9,6 +9,8 @@ module Releases
if release.destroy
update_catalog_resource!
+ execute_hooks(release, 'delete')
+
success(tag: existing_tag, release: release)
else
error(release.errors.messages || '400 Bad request', 400)
diff --git a/app/services/repositories/base_service.rb b/app/services/repositories/base_service.rb
index bf7ac2e5fd8..371ff2fc499 100644
--- a/app/services/repositories/base_service.rb
+++ b/app/services/repositories/base_service.rb
@@ -15,10 +15,6 @@ class Repositories::BaseService < BaseService
gitlab_shell.repository_exists?(repository.shard, path + '.git')
end
- def mv_repository(from_path, to_path)
- gitlab_shell.mv_repository(repository.shard, from_path, to_path)
- end
-
# If we get a Gitaly error, the repository may be corrupted. We can
# ignore these errors since we're going to trash the repositories
# anyway.
diff --git a/app/services/repositories/replicate_service.rb b/app/services/repositories/replicate_service.rb
new file mode 100644
index 00000000000..0148223910f
--- /dev/null
+++ b/app/services/repositories/replicate_service.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Repositories
+ class ReplicateService < Repositories::BaseService
+ Error = Class.new(StandardError)
+
+ def execute(new_repository, type)
+ new_repository.replicate(repository)
+
+ new_checksum = new_repository.checksum
+ checksum = repository.checksum
+
+ return if new_checksum == checksum
+
+ raise Error, format(s_(
+ 'ReplicateService|Failed to verify %{type} repository checksum from %{old} to %{new}'
+ ), type: type, old: checksum, new: new_checksum)
+ rescue StandardError => e
+ new_repository.remove
+
+ raise e
+ end
+ end
+end
diff --git a/app/services/spam/spam_verdict_service.rb b/app/services/spam/spam_verdict_service.rb
index 9efe51b43b8..2d4bebc8b2b 100644
--- a/app/services/spam/spam_verdict_service.rb
+++ b/app/services/spam/spam_verdict_service.rb
@@ -90,7 +90,7 @@ module Spam
end
def allow_possible_spam?
- target.allow_possible_spam?(user) || user.allow_possible_spam?
+ target.allow_possible_spam?(user) || user.trusted?
end
def spamcheck_client
diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb
index 04ae734a8fe..8442ff81d41 100644
--- a/app/services/system_notes/issuables_service.rb
+++ b/app/services/system_notes/issuables_service.rb
@@ -24,15 +24,16 @@ module SystemNotes
end
#
- # noteable_ref - Referenced noteable object
+ # noteable_ref - Referenced noteable object, or array of objects
#
# Example Note text:
#
# "marked this issue as related to gitlab-foss#9001"
+ # "marked this issue as related to gitlab-foss#9001, gitlab-foss#9002, and gitlab-foss#9003"
#
# Returns the created Note object
def relate_issuable(noteable_ref)
- body = "marked this #{noteable_name} as related to #{noteable_ref.to_reference(noteable.resource_parent)}"
+ body = "marked this #{noteable_name} as related to #{extract_issuable_reference(noteable_ref)}"
track_issue_event(:track_issue_related_action)
@@ -539,6 +540,14 @@ module SystemNotes
name.humanize(capitalize: false)
end
+
+ def extract_issuable_reference(reference)
+ if reference.is_a?(Array)
+ reference.map { |item| item.to_reference(noteable.resource_parent) }.to_sentence
+ else
+ reference.to_reference(noteable.resource_parent)
+ end
+ end
end
end
diff --git a/app/services/tasks_to_be_done/base_service.rb b/app/services/tasks_to_be_done/base_service.rb
deleted file mode 100644
index 1d50e5081ff..00000000000
--- a/app/services/tasks_to_be_done/base_service.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-module TasksToBeDone
- class BaseService < ::BaseContainerService
- LABEL_PREFIX = 'tasks to be done'
-
- def initialize(container:, current_user:, assignee_ids: [])
- params = {
- assignee_ids: assignee_ids,
- title: title,
- description: description,
- add_labels: label_name
- }
- super(container: container, current_user: current_user, params: params)
- end
-
- def execute
- if (issue = existing_task_issue)
- update_service = Issues::UpdateService.new(container: project, current_user: current_user, params: { add_assignee_ids: params[:assignee_ids] })
- update_service.execute(issue)
- else
- create_service = Issues::CreateService.new(container: project, current_user: current_user, params: params, perform_spam_check: false)
- create_service.execute
- end
- end
-
- private
-
- def existing_task_issue
- IssuesFinder.new(
- current_user,
- project_id: project.id,
- state: 'opened',
- non_archived: true,
- label_name: label_name
- ).execute.last
- end
-
- def title
- raise NotImplementedError
- end
-
- def description
- raise NotImplementedError
- end
-
- def label_suffix
- raise NotImplementedError
- end
-
- def label_name
- "#{LABEL_PREFIX}:#{label_suffix}"
- end
- end
-end
diff --git a/app/services/tasks_to_be_done/create_ci_task_service.rb b/app/services/tasks_to_be_done/create_ci_task_service.rb
deleted file mode 100644
index 025ca2feb8e..00000000000
--- a/app/services/tasks_to_be_done/create_ci_task_service.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-module TasksToBeDone
- class CreateCiTaskService < BaseService
- protected
-
- def title
- 'Set up CI/CD'
- end
-
- def description
- <<~DESCRIPTION
- GitLab CI/CD is a tool built into GitLab for software development through the [continuous methodologies](https://docs.gitlab.com/ee/ci/introduction/index.html#introduction-to-cicd-methodologies):
-
- * Continuous Integration (CI)
- * Continuous Delivery (CD)
- * Continuous Deployment (CD)
-
- Continuous Integration works by pushing small changes to your application’s codebase hosted in a Git repository, and, to every push, run a pipeline of scripts to build, test, and validate the code changes before merging them into the main branch.
-
- Continuous Delivery and Deployment consist of a step further CI, deploying your application to production at every push to the default branch of the repository.
-
- These methodologies allow you to catch bugs and errors early in the development cycle, ensuring that all the code deployed to production complies with the code standards you established for your app.
-
- * :book: [Read the documentation](https://docs.gitlab.com/ee/ci/introduction/index.html)
- * :clapper: [Watch a Demo](https://www.youtube.com/watch?v=1iXFbchozdY)
-
- ## Next steps
-
- * [ ] To start we recommend reviewing the following documentation:
- * [ ] [How GitLab CI/CD works.](https://docs.gitlab.com/ee/ci/introduction/index.html#how-gitlab-cicd-works)
- * [ ] [Fundamental pipeline architectures.](https://docs.gitlab.com/ee/ci/pipelines/pipeline_architectures.html)
- * [ ] [GitLab CI/CD basic workflow.](https://docs.gitlab.com/ee/ci/introduction/index.html#basic-cicd-workflow)
- * [ ] [Step-by-step guide for writing .gitlab-ci.yml for the first time.](https://docs.gitlab.com/ee/user/project/pages/getting_started_part_four.html)
- * [ ] When you're ready select **Projects** (in the top navigation bar) > **Your projects** > select the Project you've already created.
- * [ ] Select **CI / CD** in the left navigation to start setting up CI / CD in your project.
- DESCRIPTION
- end
-
- def label_suffix
- 'ci'
- end
- end
-end
diff --git a/app/services/tasks_to_be_done/create_code_task_service.rb b/app/services/tasks_to_be_done/create_code_task_service.rb
deleted file mode 100644
index dc3b9366a66..00000000000
--- a/app/services/tasks_to_be_done/create_code_task_service.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-module TasksToBeDone
- class CreateCodeTaskService < BaseService
- protected
-
- def title
- 'Create or import your code into your Project (Repository)'
- end
-
- def description
- <<~DESCRIPTION
- You've already created your Group and Project within GitLab; we'll quickly review this hierarchy below. Once you're within your project you can easily create or import repositories.
-
- **With GitLab Groups, you can:**
-
- * Create one or multiple Projects for hosting your codebase (repositories).
- * Assemble related projects together.
- * Grant members access to several projects at once.
-
- Groups can also be nested in subgroups.
-
- Read more about groups in our [documentation](https://docs.gitlab.com/ee/user/group/).
-
- **Within GitLab Projects, you can**
-
- * Use it as an issue tracker.
- * Collaborate on code.
- * Continuously build, test, and deploy your app with built-in GitLab CI/CD.
-
- You can also import an existing repository by providing the Git URL.
-
- * :book: [Read the documentation](https://docs.gitlab.com/ee/user/project/index.html).
-
- ## Next steps
-
- Create or import your first repository into the project you created:
-
- * [ ] Click **Projects** in the top navigation bar, then click **Your projects**.
- * [ ] Select the Project that you created, then select **Repository**.
- * [ ] Once on the Repository page you can select the **+** icon to add or import files.
- * [ ] You can review our full documentation on creating [repositories](https://docs.gitlab.com/ee/user/project/repository/) in GitLab.
-
- :tada: All done, you can close this issue!
- DESCRIPTION
- end
-
- def label_suffix
- 'code'
- end
- end
-end
diff --git a/app/services/tasks_to_be_done/create_issues_task_service.rb b/app/services/tasks_to_be_done/create_issues_task_service.rb
deleted file mode 100644
index a2de6852868..00000000000
--- a/app/services/tasks_to_be_done/create_issues_task_service.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-module TasksToBeDone
- class CreateIssuesTaskService < BaseService
- protected
-
- def title
- 'Create/import issues (tickets) to collaborate on ideas and plan work'
- end
-
- def description
- <<~DESCRIPTION
- Issues allow you and your team to discuss proposals before, and during, their implementation. They can be used for a variety of other purposes, customized to your needs and workflow.
-
- Issues are always associated with a specific project. If you have multiple projects in a group, you can view all the issues at the group level. [You can review our full Issue documentation here.](https://docs.gitlab.com/ee/user/project/issues/)
-
- If you have existing issues or equivalent tickets you can import them as long as they are formatted as a CSV file, [the import process is covered here](https://docs.gitlab.com/ee/user/project/issues/csv_import.html).
-
- **Common use cases include:**
-
- * Discussing the implementation of a new idea
- * Tracking tasks and work status
- * Accepting feature proposals, questions, support requests, or bug reports
- * Elaborating on new code implementations
-
- ## Next steps
-
- * [ ] Select **Projects** in the top navigation > **Your Projects** > select the Project you've already created.
- * [ ] Once you've selected that project, you can select **Issues** in the left navigation, then click **New issue**.
- * [ ] Fill in the title and description in the **New issue** page.
- * [ ] Click on **Create issue**.
-
- Pro tip: When you're in a group or project you can always utilize the **+** icon in the top navigation (located to the left of the search bar) to quickly create new issues.
-
- That's it! You can close this issue.
- DESCRIPTION
- end
-
- def label_suffix
- 'issues'
- end
- end
-end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 1f6cf2c83c9..be7405cc896 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -168,7 +168,7 @@ class TodoService
def mark_todo(target, current_user)
project = target.project
attributes = attributes_for_todo(project, target, current_user, Todo::MARKED)
- create_todos(current_user, attributes, project&.namespace, project)
+ create_todos(current_user, attributes, target_namespace(target), project)
end
def todo_exist?(issuable, current_user)
@@ -338,7 +338,7 @@ class TodoService
project = target.project
assignees = target.assignees - old_assignees
attributes = attributes_for_todo(project, target, author, Todo::ASSIGNED)
- create_todos(assignees, attributes, project.namespace, project)
+ create_todos(assignees, attributes, target_namespace(target), project)
end
end
@@ -386,6 +386,7 @@ class TodoService
attributes.merge!(target_id: nil, commit_id: target.id)
when Issue
attributes[:issue_type] = target.issue_type
+ attributes[:group] = target.namespace if target.project.blank?
when Discussion
attributes.merge!(target_type: nil, target_id: nil, discussion: target)
end
@@ -469,6 +470,11 @@ class TodoService
attributes
end
+
+ def target_namespace(target)
+ project = target.project
+ project&.namespace || target.try(:namespace)
+ end
end
TodoService.prepend_mod_with('TodoService')
diff --git a/app/services/todos/destroy/base_service.rb b/app/services/todos/destroy/base_service.rb
index ed5c4df85b1..c9c86330e1c 100644
--- a/app/services/todos/destroy/base_service.rb
+++ b/app/services/todos/destroy/base_service.rb
@@ -4,37 +4,6 @@ module Todos
module Destroy
class BaseService
def execute
- return unless todos_to_remove?
-
- ::Gitlab::Database.allow_cross_joins_across_databases(url:
- 'https://gitlab.com/gitlab-org/gitlab/-/issues/422045') do
- without_authorized(todos).delete_all
- end
- end
-
- private
-
- # rubocop: disable CodeReuse/ActiveRecord
- def without_authorized(items)
- items.where.not('todos.user_id' => authorized_users)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- # rubocop: disable CodeReuse/ActiveRecord
- def authorized_users
- ProjectAuthorization.select(:user_id).where(project_id: project_ids)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def todos
- raise NotImplementedError
- end
-
- def project_ids
- raise NotImplementedError
- end
-
- def todos_to_remove?
raise NotImplementedError
end
end
diff --git a/app/services/todos/destroy/confidential_issue_service.rb b/app/services/todos/destroy/confidential_issue_service.rb
index fadc76b1181..331c4a12681 100644
--- a/app/services/todos/destroy/confidential_issue_service.rb
+++ b/app/services/todos/destroy/confidential_issue_service.rb
@@ -9,58 +9,59 @@ module Todos
# When issue_id is passed it deletes matching todos for one confidential issue.
# When project_id is passed it deletes matching todos for all confidential issues of the project.
class ConfidentialIssueService < ::Todos::Destroy::BaseService
- extend ::Gitlab::Utils::Override
-
attr_reader :issues
- # rubocop: disable CodeReuse/ActiveRecord
def initialize(issue_id: nil, project_id: nil)
@issues =
if issue_id
- Issue.where(id: issue_id)
+ Issue.id_in(issue_id)
elsif project_id
project_confidential_issues(project_id)
end
end
- # rubocop: enable CodeReuse/ActiveRecord
+
+ def execute
+ return unless todos_to_remove?
+
+ ::Gitlab::Database.allow_cross_joins_across_databases(
+ url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/422045') do
+ delete_todos
+ end
+ end
private
+ def delete_todos
+ authorized_users = ProjectAuthorization.select(:user_id)
+ .for_project(project_ids)
+ .non_guests
+
+ todos.not_in_users(authorized_users).delete_all
+ end
+
def project_confidential_issues(project_id)
project = Project.find(project_id)
project.issues.confidential_only
end
- override :todos
# rubocop: disable CodeReuse/ActiveRecord
def todos
Todo.joins_issue_and_assignees
- .where(target: issues)
- .where(issues: { confidential: true })
+ .for_target(issues)
+ .merge(Issue.confidential_only)
.where('todos.user_id != issues.author_id')
.where('todos.user_id != issue_assignees.user_id')
end
# rubocop: enable CodeReuse/ActiveRecord
- override :todos_to_remove?
def todos_to_remove?
issues&.any?(&:confidential?)
end
- override :project_ids
def project_ids
issues&.distinct&.select(:project_id)
end
-
- override :authorized_users
- # rubocop: disable CodeReuse/ActiveRecord
- def authorized_users
- ProjectAuthorization.select(:user_id)
- .where(project_id: project_ids)
- .where('access_level >= ?', Gitlab::Access::REPORTER)
- end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/app/services/todos/destroy/group_private_service.rb b/app/services/todos/destroy/group_private_service.rb
index 60599ca9ca4..6962c204e0e 100644
--- a/app/services/todos/destroy/group_private_service.rb
+++ b/app/services/todos/destroy/group_private_service.rb
@@ -7,30 +7,34 @@ module Todos
attr_reader :group
- # rubocop: disable CodeReuse/ActiveRecord
def initialize(group_id)
- @group = Group.find_by(id: group_id)
+ @group = Group.find_by_id(group_id)
+ end
+
+ def execute
+ return unless todos_to_remove?
+
+ delete_todos
end
- # rubocop: enable CodeReuse/ActiveRecord
private
- override :todos
- # rubocop: disable CodeReuse/ActiveRecord
- def todos
- Todo.where(group_id: group.id)
+ def delete_todos
+ authorized_users = Member.from_union(
+ [
+ group.descendant_project_members_with_inactive.select(:user_id),
+ group.members_with_parents.select(:user_id)
+ ],
+ remove_duplicates: false
+ ).select(:user_id)
+
+ todos.not_in_users(authorized_users).delete_all
end
- # rubocop: enable CodeReuse/ActiveRecord
-
- override :authorized_users
- def authorized_users
- User.from_union([
- group.project_users_with_descendants.select(:id),
- group.members_with_parents.select(:user_id)
- ], remove_duplicates: false)
+
+ def todos
+ Todo.for_group(group.id)
end
- override :todos_to_remove?
def todos_to_remove?
group&.private?
end
diff --git a/app/services/todos/destroy/project_private_service.rb b/app/services/todos/destroy/project_private_service.rb
index e00d10c3780..a1ca0d8543c 100644
--- a/app/services/todos/destroy/project_private_service.rb
+++ b/app/services/todos/destroy/project_private_service.rb
@@ -7,27 +7,32 @@ module Todos
attr_reader :project
- # rubocop: disable CodeReuse/ActiveRecord
def initialize(project_id)
- @project = Project.find_by(id: project_id)
+ @project = Project.find_by_id(project_id)
+ end
+
+ def execute
+ return unless todos_to_remove?
+
+ delete_todos
end
- # rubocop: enable CodeReuse/ActiveRecord
private
- override :todos
- # rubocop: disable CodeReuse/ActiveRecord
+ def delete_todos
+ authorized_users = ProjectAuthorization.select(:user_id).for_project(project_ids)
+
+ todos.not_in_users(authorized_users).delete_all
+ end
+
def todos
- Todo.where(project_id: project.id)
+ Todo.for_project(project.id)
end
- # rubocop: enable CodeReuse/ActiveRecord
- override :project_ids
def project_ids
project.id
end
- override :todos_to_remove?
def todos_to_remove?
project&.private?
end
diff --git a/app/services/todos/destroy/unauthorized_features_service.rb b/app/services/todos/destroy/unauthorized_features_service.rb
index 513def10575..22f7a0b2a37 100644
--- a/app/services/todos/destroy/unauthorized_features_service.rb
+++ b/app/services/todos/destroy/unauthorized_features_service.rb
@@ -27,6 +27,14 @@ module Todos
private
+ def without_authorized(items)
+ items.not_in_users(authorized_users)
+ end
+
+ def authorized_users
+ ProjectAuthorization.select(:user_id).for_project(project_ids)
+ end
+
def related_todos
base_scope = Todo.for_project(project_id)
base_scope = base_scope.for_user(user_id) if user_id
diff --git a/app/services/update_container_registry_info_service.rb b/app/services/update_container_registry_info_service.rb
index 7d79b257687..b720f9b14a5 100644
--- a/app/services/update_container_registry_info_service.rb
+++ b/app/services/update_container_registry_info_service.rb
@@ -11,7 +11,7 @@ class UpdateContainerRegistryInfoService
# associated user when running this (e.g. from a rake task or a cron job),
# so we need to generate a valid JWT token with no access permissions to
# authenticate as a trusted client.
- token = Auth::ContainerRegistryAuthenticationService.access_token([], [])
+ token = Auth::ContainerRegistryAuthenticationService.access_token({})
client = ContainerRegistry::Client.new(registry_config.api_url, token: token)
info = client.registry_info
@@ -24,7 +24,8 @@ class UpdateContainerRegistryInfoService
Gitlab::CurrentSettings.update!(
container_registry_vendor: info[:vendor] || '',
container_registry_version: info[:version] || '',
- container_registry_features: info[:features] || []
+ container_registry_features: info[:features] || [],
+ container_registry_db_enabled: info[:db_enabled] || false
)
end
end
diff --git a/app/services/users/allow_possible_spam_service.rb b/app/services/users/allow_possible_spam_service.rb
deleted file mode 100644
index d9273fe0fc1..00000000000
--- a/app/services/users/allow_possible_spam_service.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Users
- class AllowPossibleSpamService < BaseService
- def initialize(current_user)
- @current_user = current_user
- end
-
- def execute(user)
- custom_attribute = {
- user_id: user.id,
- key: UserCustomAttribute::ALLOW_POSSIBLE_SPAM,
- value: "#{current_user.username}/#{current_user.id}+#{Time.current}"
- }
- UserCustomAttribute.upsert_custom_attributes([custom_attribute])
- end
- end
-end
diff --git a/app/services/users/auto_ban_service.rb b/app/services/users/auto_ban_service.rb
new file mode 100644
index 00000000000..fa3b738b4cd
--- /dev/null
+++ b/app/services/users/auto_ban_service.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Users
+ class AutoBanService < BaseService
+ def initialize(user:, reason:)
+ @user = user
+ @reason = reason
+ end
+
+ def execute
+ if user.ban
+ record_custom_attribute
+ success
+ else
+ messages = user.errors.full_messages
+ error(messages.uniq.join('. '))
+ end
+ end
+
+ private
+
+ attr_reader :user, :reason
+
+ def record_custom_attribute
+ custom_attribute = {
+ user_id: user.id,
+ key: UserCustomAttribute::AUTO_BANNED_BY,
+ value: reason
+ }
+ UserCustomAttribute.upsert_custom_attributes([custom_attribute])
+ end
+ end
+end
diff --git a/app/services/users/in_product_marketing_email_records.rb b/app/services/users/in_product_marketing_email_records.rb
index 94dbd809496..fcb252536b3 100644
--- a/app/services/users/in_product_marketing_email_records.rb
+++ b/app/services/users/in_product_marketing_email_records.rb
@@ -13,10 +13,9 @@ module Users
@records = []
end
- def add(user, campaign: nil, track: nil, series: nil)
+ def add(user, track: nil, series: nil)
@records << Users::InProductMarketingEmail.new(
user: user,
- campaign: campaign,
track: track,
series: series,
created_at: Time.zone.now,
diff --git a/app/services/users/set_namespace_commit_email_service.rb b/app/services/users/set_namespace_commit_email_service.rb
index 30ee597120d..775db364625 100644
--- a/app/services/users/set_namespace_commit_email_service.rb
+++ b/app/services/users/set_namespace_commit_email_service.rb
@@ -20,7 +20,7 @@ module Users
return error(_("User doesn't exist or you don't have permission to change namespace commit emails."))
end
- unless can?(target_user, :read_namespace, namespace)
+ unless can?(target_user, :read_namespace_via_membership, namespace)
return error(_("Namespace doesn't exist or you don't have permission."))
end
diff --git a/app/services/users/signup_service.rb b/app/services/users/signup_service.rb
deleted file mode 100644
index 9eb1e75988c..00000000000
--- a/app/services/users/signup_service.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module Users
- class SignupService < BaseService
- def initialize(current_user, params = {})
- @user = current_user
- @params = params.dup
- end
-
- def execute
- assign_attributes
- inject_validators
-
- if @user.save
- ServiceResponse.success
- else
- ServiceResponse.error(message: @user.errors.full_messages.join('. '))
- end
- end
-
- private
-
- def assign_attributes
- @user.assign_attributes(params) unless params.empty?
- end
-
- def inject_validators
- class << @user
- validates :role, presence: true
- validates :setup_for_company, inclusion: { in: [true, false], message: :blank } if Gitlab.com?
- end
- end
- end
-end
diff --git a/app/services/users/disallow_possible_spam_service.rb b/app/services/users/trust_service.rb
index e31ba7ddff0..faf0b9c40ea 100644
--- a/app/services/users/disallow_possible_spam_service.rb
+++ b/app/services/users/trust_service.rb
@@ -1,13 +1,14 @@
# frozen_string_literal: true
module Users
- class DisallowPossibleSpamService < BaseService
+ class TrustService < BaseService
def initialize(current_user)
@current_user = current_user
end
def execute(user)
- user.custom_attributes.by_key(UserCustomAttribute::ALLOW_POSSIBLE_SPAM).delete_all
+ UserCustomAttribute.set_trusted_by(user: user, trusted_by: @current_user)
+ success
end
end
end
diff --git a/app/services/users/untrust_service.rb b/app/services/users/untrust_service.rb
new file mode 100644
index 00000000000..aa5de71b97f
--- /dev/null
+++ b/app/services/users/untrust_service.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Users
+ class UntrustService < BaseService
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(user)
+ user.trusted_with_spam_attribute.delete
+ success
+ end
+ end
+end
diff --git a/app/services/verify_pages_domain_service.rb b/app/services/verify_pages_domain_service.rb
index 408ee429a74..59c73aa929c 100644
--- a/app/services/verify_pages_domain_service.rb
+++ b/app/services/verify_pages_domain_service.rb
@@ -46,9 +46,15 @@ class VerifyPagesDomainService < BaseService
notify(:verification_succeeded)
end
+ after_successful_verification
+
success
end
+ def after_successful_verification
+ # method overridden in EE
+ end
+
def unverify_domain!
was_verified = domain.verified?
@@ -115,3 +121,5 @@ class VerifyPagesDomainService < BaseService
notification_service.public_send("pages_domain_#{type}", domain) # rubocop:disable GitlabSecurity/PublicSend
end
end
+
+VerifyPagesDomainService.prepend_mod
diff --git a/app/services/vs_code/settings/create_or_update_service.rb b/app/services/vs_code/settings/create_or_update_service.rb
new file mode 100644
index 00000000000..27688b911b7
--- /dev/null
+++ b/app/services/vs_code/settings/create_or_update_service.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module VsCode
+ module Settings
+ class CreateOrUpdateService
+ def initialize(current_user:, params: {})
+ @current_user = current_user
+ @params = params
+ end
+
+ def execute
+ # The GitLab VSCode settings API does not support creating or updating
+ # machines.
+ return ServiceResponse.success(payload: DEFAULT_MACHINE) if @params[:setting_type] == 'machines'
+
+ setting = VsCodeSetting.by_user(current_user).by_setting_type(params[:setting_type]).first
+
+ if setting.nil?
+ merged_params = params.merge(user: current_user, uuid: SecureRandom.uuid)
+ setting = VsCodeSetting.new(merged_params)
+ else
+ setting.content = params[:content]
+ setting.uuid = SecureRandom.uuid
+ end
+
+ if setting.save
+ ServiceResponse.success(payload: setting)
+ else
+ ServiceResponse.error(
+ message: setting.errors.full_messages.to_sentence,
+ payload: { setting: setting }
+ )
+ end
+ end
+
+ private
+
+ attr_reader :current_user, :params
+ end
+ end
+end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 5bad2a1583c..27b29feed50 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -36,7 +36,9 @@ class WebHookService
attr_accessor :hook, :data, :hook_name, :request_options
attr_reader :uniqueness_token
- def self.hook_to_event(hook_name)
+ def self.hook_to_event(hook_name, hook = nil)
+ return hook.class.name.titleize if hook.is_a?(SystemHook)
+
hook_name.to_s.singularize.titleize
end
@@ -194,7 +196,7 @@ class WebHookService
headers = {
'Content-Type' => 'application/json',
'User-Agent' => "GitLab/#{Gitlab::VERSION}",
- Gitlab::WebHooks::GITLAB_EVENT_HEADER => self.class.hook_to_event(hook_name),
+ Gitlab::WebHooks::GITLAB_EVENT_HEADER => self.class.hook_to_event(hook_name, hook),
Gitlab::WebHooks::GITLAB_UUID_HEADER => SecureRandom.uuid,
Gitlab::WebHooks::GITLAB_INSTANCE_HEADER => Gitlab.config.gitlab.base_url
}
diff --git a/app/services/work_items/related_work_item_links/create_service.rb b/app/services/work_items/related_work_item_links/create_service.rb
index f313881470a..38e5ba3be7f 100644
--- a/app/services/work_items/related_work_item_links/create_service.rb
+++ b/app/services/work_items/related_work_item_links/create_service.rb
@@ -9,6 +9,7 @@ module WorkItems
return error(_('No matching work item found.'), 404) unless can?(current_user, :admin_work_item_link, issuable)
response = super
+ create_notes_async if new_links.any?
if response[:status] == :success
response[:message] = format(
@@ -30,6 +31,10 @@ module WorkItems
private
+ def create_notes(_issuable_link)
+ # no-op notes are created asynchronously
+ end
+
def link_class
WorkItems::RelatedWorkItemLink
end
@@ -49,9 +54,23 @@ module WorkItems
created_links.collect(&:target_id)
end
+ def create_notes_async
+ link_ids = new_links.collect(&:id)
+
+ worker_params = {
+ issuable_class: issuable.class.name,
+ issuable_id: issuable.id,
+ link_ids: link_ids,
+ link_type: params[:link_type] || 'relates_to',
+ user_id: current_user.id
+ }
+
+ Issuable::RelatedLinksCreateWorker.perform_async(worker_params)
+ end
+
override :issuables_already_assigned_message
def issuables_already_assigned_message
- _('Work items are already linked')
+ _('Items are already linked')
end
override :issuables_not_found_message
diff --git a/app/services/work_items/widgets/labels_service/update_service.rb b/app/services/work_items/widgets/labels_service/update_service.rb
index b880398677d..b0791571924 100644
--- a/app/services/work_items/widgets/labels_service/update_service.rb
+++ b/app/services/work_items/widgets/labels_service/update_service.rb
@@ -11,6 +11,7 @@ module WorkItems
end
return if params.blank?
+ return unless has_permission?(:set_work_item_metadata)
service_params.merge!(params.slice(:add_label_ids, :remove_label_ids))
end
diff --git a/app/services/work_items/widgets/start_and_due_date_service/update_service.rb b/app/services/work_items/widgets/start_and_due_date_service/update_service.rb
index 0dbf3aa31d9..5d47b3a1516 100644
--- a/app/services/work_items/widgets/start_and_due_date_service/update_service.rb
+++ b/app/services/work_items/widgets/start_and_due_date_service/update_service.rb
@@ -8,6 +8,7 @@ module WorkItems
return widget.work_item.assign_attributes({ start_date: nil, due_date: nil }) if new_type_excludes_widget?
return if params.blank?
+ return unless has_permission?(:set_work_item_metadata)
widget.work_item.assign_attributes(params.slice(:start_date, :due_date))
end