summaryrefslogtreecommitdiff
path: root/app/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/services')
-rw-r--r--app/services/auth/container_registry_authentication_service.rb22
-rw-r--r--app/services/boards/issues/list_service.rb5
-rw-r--r--app/services/boards/issues/move_service.rb5
-rw-r--r--app/services/boards/lists/create_service.rb8
-rw-r--r--app/services/ci/register_job_service.rb19
-rw-r--r--app/services/deploy_tokens/create_service.rb7
-rw-r--r--app/services/issuable/destroy_service.rb1
-rw-r--r--app/services/issuable_base_service.rb5
-rw-r--r--app/services/issues/close_service.rb1
-rw-r--r--app/services/issues/update_service.rb17
-rw-r--r--app/services/notes/post_process_service.rb10
-rw-r--r--app/services/notification_recipient_service.rb3
-rw-r--r--app/services/projects/autocomplete_service.rb3
-rw-r--r--app/services/projects/base_move_relations_service.rb22
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb28
-rw-r--r--app/services/projects/gitlab_projects_import_service.rb26
-rw-r--r--app/services/projects/import_export/export_service.rb37
-rw-r--r--app/services/projects/import_service.rb8
-rw-r--r--app/services/projects/move_access_service.rb25
-rw-r--r--app/services/projects/move_deploy_keys_projects_service.rb31
-rw-r--r--app/services/projects/move_forks_service.rb42
-rw-r--r--app/services/projects/move_lfs_objects_projects_service.rb29
-rw-r--r--app/services/projects/move_notification_settings_service.rb38
-rw-r--r--app/services/projects/move_project_authorizations_service.rb40
-rw-r--r--app/services/projects/move_project_group_links_service.rb40
-rw-r--r--app/services/projects/move_project_members_service.rb40
-rw-r--r--app/services/projects/move_users_star_projects_service.rb20
-rw-r--r--app/services/projects/overwrite_project_service.rb69
-rw-r--r--app/services/projects/transfer_service.rb2
-rw-r--r--app/services/projects/update_pages_service.rb36
-rw-r--r--app/services/quick_actions/interpret_service.rb5
-rw-r--r--app/services/system_note_service.rb4
33 files changed, 587 insertions, 63 deletions
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 2b77f6be72a..f28cddb2af3 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -109,7 +109,7 @@ module Auth
case requested_action
when 'pull'
- build_can_pull?(requested_project) || user_can_pull?(requested_project)
+ build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project)
when 'push'
build_can_push?(requested_project) || user_can_push?(requested_project)
when '*'
@@ -123,22 +123,34 @@ module Auth
Gitlab.config.registry
end
+ def can_user?(ability, project)
+ user = current_user.is_a?(User) ? current_user : nil
+ can?(user, ability, project)
+ end
+
def build_can_pull?(requested_project)
# Build can:
# 1. pull from its own project (for ex. a build)
# 2. read images from dependent projects if creator of build is a team member
has_authentication_ability?(:build_read_container_image) &&
- (requested_project == project || can?(current_user, :build_read_container_image, requested_project))
+ (requested_project == project || can_user?(:build_read_container_image, requested_project))
end
def user_can_admin?(requested_project)
has_authentication_ability?(:admin_container_image) &&
- can?(current_user, :admin_container_image, requested_project)
+ can_user?(:admin_container_image, requested_project)
end
def user_can_pull?(requested_project)
has_authentication_ability?(:read_container_image) &&
- can?(current_user, :read_container_image, requested_project)
+ can_user?(:read_container_image, requested_project)
+ end
+
+ def deploy_token_can_pull?(requested_project)
+ has_authentication_ability?(:read_container_image) &&
+ current_user.is_a?(DeployToken) &&
+ current_user.has_access_to?(requested_project) &&
+ current_user.read_registry?
end
##
@@ -154,7 +166,7 @@ module Auth
def user_can_push?(requested_project)
has_authentication_ability?(:create_container_image) &&
- can?(current_user, :create_container_image, requested_project)
+ can_user?(:create_container_image, requested_project)
end
def error(code, status:, message: '')
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index ecd74b74f8a..ac70a99c2c5 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -35,6 +35,7 @@ module Boards
def filter_params
set_parent
set_state
+ set_scope
params
end
@@ -51,6 +52,10 @@ module Boards
params[:state] = list && list.closed? ? 'closed' : 'opened'
end
+ def set_scope
+ params[:include_subgroups] = board.group_board?
+ end
+
def board_label_ids
@board_label_ids ||= board.lists.movable.pluck(:label_id)
end
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 15fed7d17c1..3ceab209f3f 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -42,7 +42,10 @@ module Boards
)
end
- attrs[:move_between_ids] = move_between_ids if move_between_ids
+ if move_between_ids
+ attrs[:move_between_ids] = move_between_ids
+ attrs[:board_group_id] = board.group&.id
+ end
attrs
end
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index bebc90c7a8d..02f1c709374 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -12,11 +12,15 @@ module Boards
private
def available_labels_for(board)
+ options = { include_ancestor_groups: true }
+
if board.group_board?
- parent.labels
+ options.merge!(group_id: parent.id, only_group_labels: true)
else
- LabelsFinder.new(current_user, project_id: parent.id).execute
+ options[:project_id] = parent.id
end
+
+ LabelsFinder.new(current_user, options).execute
end
def next_position(board)
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index e09b445636f..d46dcff34a1 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -4,6 +4,9 @@ module Ci
class RegisterJobService
attr_reader :runner
+ JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30].freeze
+ JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
+
Result = Struct.new(:build, :valid?)
def initialize(runner)
@@ -104,10 +107,22 @@ module Ci
end
def register_success(job)
- job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
+ labels = { shared_runner: runner.shared?,
+ jobs_running_for_project: jobs_running_for_project(job) }
+
+ job_queue_duration_seconds.observe(labels, Time.now - job.queued_at)
attempt_counter.increment
end
+ def jobs_running_for_project(job)
+ return '+Inf' unless runner.shared?
+
+ # excluding currently started job
+ running_jobs_count = job.project.builds.running.where(runner: Ci::Runner.shared)
+ .limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1
+ running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+"
+ end
+
def failed_attempt_counter
@failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
end
@@ -117,7 +132,7 @@ module Ci
end
def job_queue_duration_seconds
- @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time')
+ @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time', {}, JOB_QUEUE_DURATION_SECONDS_BUCKETS)
end
end
end
diff --git a/app/services/deploy_tokens/create_service.rb b/app/services/deploy_tokens/create_service.rb
new file mode 100644
index 00000000000..52f545947af
--- /dev/null
+++ b/app/services/deploy_tokens/create_service.rb
@@ -0,0 +1,7 @@
+module DeployTokens
+ class CreateService < BaseService
+ def execute
+ @project.deploy_tokens.create(params)
+ end
+ end
+end
diff --git a/app/services/issuable/destroy_service.rb b/app/services/issuable/destroy_service.rb
index 7197a426a72..0b1a33518c6 100644
--- a/app/services/issuable/destroy_service.rb
+++ b/app/services/issuable/destroy_service.rb
@@ -4,6 +4,7 @@ module Issuable
TodoService.new.destroy_target(issuable) do |issuable|
if issuable.destroy
issuable.update_project_counter_caches
+ issuable.assignees.each(&:invalidate_cache_counts)
end
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 02fb48108fb..1f67e3ecf9d 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -51,9 +51,10 @@ class IssuableBaseService < BaseService
return unless milestone_id
params[:milestone_id] = '' if milestone_id == IssuableFinder::NONE
+ group_ids = project.group&.self_and_ancestors&.pluck(:id)
milestone =
- Milestone.for_projects_and_groups([project.id], [project.group&.id]).find_by_id(milestone_id)
+ Milestone.for_projects_and_groups([project.id], group_ids).find_by_id(milestone_id)
params[:milestone_id] = '' unless milestone
end
@@ -106,7 +107,7 @@ class IssuableBaseService < BaseService
end
def available_labels
- @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
+ @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: true).execute
end
def handle_quick_actions_on_create(issuable)
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 0c5cf2c62ad..fee5bc38f7b 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -23,6 +23,7 @@ module Issues
end
if project.issues_enabled? && issue.close
+ issue.update(closed_by: current_user)
event_service.close_issue(issue, current_user)
create_note(issue, commit) if system_note
notification_service.close_issue(issue, current_user) if notifications
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index d7aa7e2347e..1374f10c586 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -55,9 +55,10 @@ module Issues
return unless params[:move_between_ids]
after_id, before_id = params.delete(:move_between_ids)
+ board_group_id = params.delete(:board_group_id)
- issue_before = get_issue_if_allowed(issue.project, before_id) if before_id
- issue_after = get_issue_if_allowed(issue.project, after_id) if after_id
+ issue_before = get_issue_if_allowed(before_id, board_group_id)
+ issue_after = get_issue_if_allowed(after_id, board_group_id)
issue.move_between(issue_before, issue_after)
end
@@ -84,8 +85,16 @@ module Issues
private
- def get_issue_if_allowed(project, id)
- issue = project.issues.find(id)
+ def get_issue_if_allowed(id, board_group_id = nil)
+ return unless id
+
+ issue =
+ if board_group_id
+ IssuesFinder.new(current_user, group_id: board_group_id, include_subgroups: true).find_by(id: id)
+ else
+ project.issues.find(id)
+ end
+
issue if can?(current_user, :update_issue, issue)
end
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index ad3dcc5010b..199b8028dbc 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -11,7 +11,7 @@ module Notes
unless @note.system?
EventCreateService.new.leave_note(@note, @note.author)
- return unless @note.for_project_noteable?
+ return if @note.for_personal_snippet?
@note.create_cross_references!
execute_note_hooks
@@ -23,9 +23,13 @@ module Notes
end
def execute_note_hooks
+ return unless @note.project
+
note_data = hook_data
- @note.project.execute_hooks(note_data, :note_hooks)
- @note.project.execute_services(note_data, :note_hooks)
+ hooks_scope = @note.confidential? ? :confidential_note_hooks : :note_hooks
+
+ @note.project.execute_hooks(note_data, hooks_scope)
+ @note.project.execute_services(note_data, hooks_scope)
end
end
end
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index e4be953e810..b82d9c64296 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -54,8 +54,7 @@ module NotificationRecipientService
users = users.includes(:notification_settings)
end
- users = Array(users)
- users.compact!
+ users = Array(users).compact
recipients.concat(users.map { |u| make_recipient(u, type, reason) })
end
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index e61ecb696d0..346971138b1 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -21,7 +21,8 @@ module Projects
end
def labels(target = nil)
- labels = LabelsFinder.new(current_user, project_id: project.id).execute.select([:color, :title])
+ labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true)
+ .execute.select([:color, :title])
return labels unless target&.respond_to?(:labels)
diff --git a/app/services/projects/base_move_relations_service.rb b/app/services/projects/base_move_relations_service.rb
new file mode 100644
index 00000000000..e8fd3ef57e5
--- /dev/null
+++ b/app/services/projects/base_move_relations_service.rb
@@ -0,0 +1,22 @@
+module Projects
+ class BaseMoveRelationsService < BaseService
+ attr_reader :source_project
+ def execute(source_project, remove_remaining_elements: true)
+ return if source_project.blank?
+
+ @source_project = source_project
+
+ true
+ end
+
+ private
+
+ def prepare_relation(relation, id_param = :id)
+ if Gitlab::Database.postgresql?
+ relation
+ else
+ relation.model.where("#{id_param}": relation.pluck(id_param))
+ end
+ end
+ end
+end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 633e2c8236c..d361d070993 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -96,6 +96,8 @@ module Projects
system_hook_service.execute_hooks_for(@project, :create)
setup_authorizations
+
+ current_user.invalidate_personal_projects_count
end
# Refresh the current user's authorizations inline (so they can access the
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 4b8f955ae69..aa14206db3b 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -34,6 +34,8 @@ module Projects
system_hook_service.execute_hooks_for(project, :destroy)
log_info("Project \"#{project.full_path}\" was removed")
+ current_user.invalidate_personal_projects_count
+
true
rescue => error
attempt_rollback(project, error.message)
@@ -44,6 +46,20 @@ module Projects
raise
end
+ def attempt_repositories_rollback
+ return unless @project
+
+ flush_caches(@project)
+
+ unless mv_repository(removal_path(repo_path), repo_path)
+ raise_error('Failed to restore project repository. Please contact the administrator.')
+ end
+
+ unless mv_repository(removal_path(wiki_path), wiki_path)
+ raise_error('Failed to restore wiki repository. Please contact the administrator.')
+ end
+ end
+
private
def repo_path
@@ -68,12 +84,9 @@ module Projects
# Skip repository removal. We use this flag when remove user or group
return true if params[:skip_repo] == true
- # There is a possibility project does not have repository or wiki
- return true unless gitlab_shell.exists?(project.repository_storage_path, path + '.git')
-
new_path = removal_path(path)
- if gitlab_shell.mv_repository(project.repository_storage_path, path, new_path)
+ if mv_repository(path, new_path)
log_info("Repository \"#{path}\" moved to \"#{new_path}\"")
project.run_after_commit do
@@ -85,6 +98,13 @@ module Projects
end
end
+ def mv_repository(from_path, to_path)
+ # There is a possibility project does not have repository or wiki
+ return true unless gitlab_shell.exists?(project.repository_storage_path, from_path + '.git')
+
+ gitlab_shell.mv_repository(project.repository_storage_path, from_path, to_path)
+ end
+
def attempt_rollback(project, message)
return unless project
diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb
index a68ecb4abe1..a16268f4fd2 100644
--- a/app/services/projects/gitlab_projects_import_service.rb
+++ b/app/services/projects/gitlab_projects_import_service.rb
@@ -5,8 +5,8 @@ module Projects
class GitlabProjectsImportService
attr_reader :current_user, :params
- def initialize(user, params)
- @current_user, @params = user, params.dup
+ def initialize(user, import_params, override_params = nil)
+ @current_user, @params, @override_params = user, import_params.dup, override_params
end
def execute
@@ -15,8 +15,18 @@ module Projects
file = params.delete(:file)
FileUtils.copy_entry(file.path, import_upload_path)
+ @overwrite = params.delete(:overwrite)
+ data = {}
+ data[:override_params] = @override_params if @override_params
+
+ if overwrite_project?
+ data[:original_path] = params[:path]
+ params[:path] += "-#{tmp_filename}"
+ end
+
params[:import_type] = 'gitlab_project'
params[:import_source] = import_upload_path
+ params[:import_data] = { data: data } if data.present?
::Projects::CreateService.new(current_user, params).execute
end
@@ -30,5 +40,17 @@ module Projects
def tmp_filename
SecureRandom.hex
end
+
+ def overwrite_project?
+ @overwrite && project_with_same_full_path?
+ end
+
+ def project_with_same_full_path?
+ Project.find_by_full_path("#{current_namespace.full_path}/#{params[:path]}").present?
+ end
+
+ def current_namespace
+ @current_namespace ||= Namespace.find_by(id: params[:namespace_id])
+ end
end
end
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index d16aa3de639..7bf0b90b491 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -1,22 +1,36 @@
module Projects
module ImportExport
class ExportService < BaseService
- def execute(_options = {})
+ def execute(after_export_strategy = nil, options = {})
@shared = project.import_export_shared
- save_all
+
+ save_all!
+ execute_after_export_action(after_export_strategy)
end
private
- def save_all
- if [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
+ def execute_after_export_action(after_export_strategy)
+ return unless after_export_strategy
+
+ unless after_export_strategy.execute(current_user, project)
+ cleanup_and_notify_error
+ end
+ end
+
+ def save_all!
+ if save_services
Gitlab::ImportExport::Saver.save(project: project, shared: @shared)
notify_success
else
- cleanup_and_notify
+ cleanup_and_notify_error!
end
end
+ def save_services
+ [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver].all?(&:save)
+ end
+
def version_saver
Gitlab::ImportExport::VersionSaver.new(shared: @shared)
end
@@ -41,19 +55,26 @@ module Projects
Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared)
end
- def cleanup_and_notify
+ def lfs_saver
+ Gitlab::ImportExport::LfsSaver.new(project: project, shared: @shared)
+ end
+
+ def cleanup_and_notify_error
Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}")
FileUtils.rm_rf(@shared.export_path)
notify_error
+ end
+
+ def cleanup_and_notify_error!
+ cleanup_and_notify_error
+
raise Gitlab::ImportExport::Error.new(@shared.errors.join(', '))
end
def notify_success
Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported")
-
- notification_service.project_exported(@project, @current_user)
end
def notify_error
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index a34024f4f80..bdd9598f85a 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -28,7 +28,11 @@ module Projects
def add_repository_to_project
if project.external_import? && !unknown_url?
- raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url, valid_ports: Project::VALID_IMPORT_PORTS)
+ begin
+ Gitlab::UrlBlocker.validate!(project.import_url, valid_ports: Project::VALID_IMPORT_PORTS)
+ rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ raise Error, "Blocked import URL: #{e.message}"
+ end
end
# We should skip the repository for a GitHub import or GitLab project import,
@@ -57,7 +61,7 @@ module Projects
project.ensure_repository
project.repository.fetch_as_mirror(project.import_url, refmap: refmap)
else
- gitlab_shell.import_repository(project.repository_storage_path, project.disk_path, project.import_url)
+ gitlab_shell.import_repository(project.repository_storage, project.disk_path, project.import_url)
end
rescue Gitlab::Shell::Error, Gitlab::Git::RepositoryMirroring::RemoteError => e
# Expire cache to prevent scenarios such as:
diff --git a/app/services/projects/move_access_service.rb b/app/services/projects/move_access_service.rb
new file mode 100644
index 00000000000..3af3a22d486
--- /dev/null
+++ b/app/services/projects/move_access_service.rb
@@ -0,0 +1,25 @@
+module Projects
+ class MoveAccessService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ @project.with_transaction_returning_status do
+ if @project.namespace != source_project.namespace
+ @project.run_after_commit do
+ source_project.namespace.refresh_project_authorizations
+ self.namespace.refresh_project_authorizations
+ end
+ end
+
+ ::Projects::MoveProjectMembersService.new(@project, @current_user)
+ .execute(source_project, remove_remaining_elements: remove_remaining_elements)
+ ::Projects::MoveProjectGroupLinksService.new(@project, @current_user)
+ .execute(source_project, remove_remaining_elements: remove_remaining_elements)
+ ::Projects::MoveProjectAuthorizationsService.new(@project, @current_user)
+ .execute(source_project, remove_remaining_elements: remove_remaining_elements)
+
+ success
+ end
+ end
+ end
+end
diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb
new file mode 100644
index 00000000000..dde420655b0
--- /dev/null
+++ b/app/services/projects/move_deploy_keys_projects_service.rb
@@ -0,0 +1,31 @@
+module Projects
+ class MoveDeployKeysProjectsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_deploy_keys_projects
+ remove_remaining_deploy_keys_projects if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_deploy_keys_projects
+ prepare_relation(non_existent_deploy_keys_projects)
+ .update_all(project_id: @project.id)
+ end
+
+ def non_existent_deploy_keys_projects
+ source_project.deploy_keys_projects
+ .joins(:deploy_key)
+ .where.not(keys: { fingerprint: @project.deploy_keys.select(:fingerprint) })
+ end
+
+ def remove_remaining_deploy_keys_projects
+ source_project.deploy_keys_projects.destroy_all
+ end
+ end
+end
diff --git a/app/services/projects/move_forks_service.rb b/app/services/projects/move_forks_service.rb
new file mode 100644
index 00000000000..d2901ea1457
--- /dev/null
+++ b/app/services/projects/move_forks_service.rb
@@ -0,0 +1,42 @@
+module Projects
+ class MoveForksService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super && source_project.fork_network
+
+ Project.transaction(requires_new: true) do
+ move_forked_project_links
+ move_fork_network_members
+ update_root_project
+ refresh_forks_count
+
+ success
+ end
+ end
+
+ private
+
+ def move_forked_project_links
+ # Update ancestor
+ ForkedProjectLink.where(forked_to_project: source_project)
+ .update_all(forked_to_project_id: @project.id)
+
+ # Update the descendants
+ ForkedProjectLink.where(forked_from_project: source_project)
+ .update_all(forked_from_project_id: @project.id)
+ end
+
+ def move_fork_network_members
+ ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id)
+ ForkNetworkMember.where(forked_from_project: source_project).update_all(forked_from_project_id: @project.id)
+ end
+
+ def update_root_project
+ # Update root network project
+ ForkNetwork.where(root_project: source_project).update_all(root_project_id: @project.id)
+ end
+
+ def refresh_forks_count
+ Projects::ForksCountService.new(@project).refresh_cache
+ end
+ end
+end
diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb
new file mode 100644
index 00000000000..298da5f1a82
--- /dev/null
+++ b/app/services/projects/move_lfs_objects_projects_service.rb
@@ -0,0 +1,29 @@
+module Projects
+ class MoveLfsObjectsProjectsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_lfs_objects_projects
+ remove_remaining_lfs_objects_project if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_lfs_objects_projects
+ prepare_relation(non_existent_lfs_objects_projects)
+ .update_all(project_id: @project.lfs_storage_project.id)
+ end
+
+ def remove_remaining_lfs_objects_project
+ source_project.lfs_objects_projects.destroy_all
+ end
+
+ def non_existent_lfs_objects_projects
+ source_project.lfs_objects_projects.where.not(lfs_object: @project.lfs_objects)
+ end
+ end
+end
diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb
new file mode 100644
index 00000000000..f7be461a5da
--- /dev/null
+++ b/app/services/projects/move_notification_settings_service.rb
@@ -0,0 +1,38 @@
+module Projects
+ class MoveNotificationSettingsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_notification_settings
+ remove_remaining_notification_settings if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_notification_settings
+ prepare_relation(non_existent_notifications)
+ .update_all(source_id: @project.id)
+ end
+
+ # Remove remaining notification settings from source_project
+ def remove_remaining_notification_settings
+ source_project.notification_settings.destroy_all
+ end
+
+ # Get users of current notification_settings
+ def users_in_target_project
+ @project.notification_settings.select(:user_id)
+ end
+
+ # Look for notification_settings in source_project that are not in the target project
+ def non_existent_notifications
+ source_project.notification_settings
+ .select(:id)
+ .where.not(user_id: users_in_target_project)
+ end
+ end
+end
diff --git a/app/services/projects/move_project_authorizations_service.rb b/app/services/projects/move_project_authorizations_service.rb
new file mode 100644
index 00000000000..5ef12fc49e5
--- /dev/null
+++ b/app/services/projects/move_project_authorizations_service.rb
@@ -0,0 +1,40 @@
+# NOTE: This service cannot be used directly because it is part of a
+# a bigger process. Instead, use the service MoveAccessService which moves
+# project memberships, project group links, authorizations and refreshes
+# the authorizations if neccessary
+module Projects
+ class MoveProjectAuthorizationsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_project_authorizations
+
+ remove_remaining_authorizations if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_project_authorizations
+ prepare_relation(non_existent_authorization, :user_id)
+ .update_all(project_id: @project.id)
+ end
+
+ def remove_remaining_authorizations
+ # I think because the Project Authorization table does not have a primary key
+ # it brings a lot a problems/bugs. First, Rails raises PG::SyntaxException if we use
+ # destroy_all instead of delete_all.
+ source_project.project_authorizations.delete_all(:delete_all)
+ end
+
+ # Look for authorizations in source_project that are not in the target project
+ def non_existent_authorization
+ source_project.project_authorizations
+ .select(:user_id)
+ .where.not(user: @project.authorized_users)
+ end
+ end
+end
diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb
new file mode 100644
index 00000000000..dbeffd7dae9
--- /dev/null
+++ b/app/services/projects/move_project_group_links_service.rb
@@ -0,0 +1,40 @@
+# NOTE: This service cannot be used directly because it is part of a
+# a bigger process. Instead, use the service MoveAccessService which moves
+# project memberships, project group links, authorizations and refreshes
+# the authorizations if neccessary
+module Projects
+ class MoveProjectGroupLinksService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_group_links
+ remove_remaining_project_group_links if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_group_links
+ prepare_relation(non_existent_group_links)
+ .update_all(project_id: @project.id)
+ end
+
+ # Remove remaining project group links from source_project
+ def remove_remaining_project_group_links
+ source_project.reload.project_group_links.destroy_all
+ end
+
+ def group_links_in_target_project
+ @project.project_group_links.select(:group_id)
+ end
+
+ # Look for groups in source_project that are not in the target project
+ def non_existent_group_links
+ source_project.project_group_links
+ .where.not(group_id: group_links_in_target_project)
+ end
+ end
+end
diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb
new file mode 100644
index 00000000000..22a5f0a3fe6
--- /dev/null
+++ b/app/services/projects/move_project_members_service.rb
@@ -0,0 +1,40 @@
+# NOTE: This service cannot be used directly because it is part of a
+# a bigger process. Instead, use the service MoveAccessService which moves
+# project memberships, project group links, authorizations and refreshes
+# the authorizations if neccessary
+module Projects
+ class MoveProjectMembersService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ Project.transaction(requires_new: true) do
+ move_project_members
+ remove_remaining_members if remove_remaining_elements
+
+ success
+ end
+ end
+
+ private
+
+ def move_project_members
+ prepare_relation(non_existent_members).update_all(source_id: @project.id)
+ end
+
+ def remove_remaining_members
+ # Remove remaining members and authorizations from source_project
+ source_project.project_members.destroy_all
+ end
+
+ def project_members_in_target_project
+ @project.project_members.select(:user_id)
+ end
+
+ # Look for members in source_project that are not in the target project
+ def non_existent_members
+ source_project.members
+ .select(:id)
+ .where.not(user_id: @project.project_members.select(:user_id))
+ end
+ end
+end
diff --git a/app/services/projects/move_users_star_projects_service.rb b/app/services/projects/move_users_star_projects_service.rb
new file mode 100644
index 00000000000..079fd5b9685
--- /dev/null
+++ b/app/services/projects/move_users_star_projects_service.rb
@@ -0,0 +1,20 @@
+module Projects
+ class MoveUsersStarProjectsService < BaseMoveRelationsService
+ def execute(source_project, remove_remaining_elements: true)
+ return unless super
+
+ user_stars = source_project.users_star_projects
+
+ return unless user_stars.any?
+
+ Project.transaction(requires_new: true) do
+ user_stars.update_all(project_id: @project.id)
+
+ Project.reset_counters @project.id, :users_star_projects
+ Project.reset_counters source_project.id, :users_star_projects
+
+ success
+ end
+ end
+ end
+end
diff --git a/app/services/projects/overwrite_project_service.rb b/app/services/projects/overwrite_project_service.rb
new file mode 100644
index 00000000000..ce94f147aa9
--- /dev/null
+++ b/app/services/projects/overwrite_project_service.rb
@@ -0,0 +1,69 @@
+module Projects
+ class OverwriteProjectService < BaseService
+ def execute(source_project)
+ return unless source_project && source_project.namespace == @project.namespace
+
+ Project.transaction do
+ move_before_destroy_relationships(source_project)
+ destroy_old_project(source_project)
+ rename_project(source_project.name, source_project.path)
+
+ @project
+ end
+ # Projects::DestroyService can raise Exceptions, but we don't want
+ # to pass that kind of exception to the caller. Instead, we change it
+ # for a StandardError exception
+ rescue Exception => e # rubocop:disable Lint/RescueException
+ attempt_restore_repositories(source_project)
+
+ if e.class == Exception
+ raise StandardError, e.message
+ else
+ raise
+ end
+ end
+
+ private
+
+ def move_before_destroy_relationships(source_project)
+ options = { remove_remaining_elements: false }
+
+ ::Projects::MoveUsersStarProjectsService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveAccessService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveDeployKeysProjectsService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveNotificationSettingsService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveForksService.new(@project, @current_user).execute(source_project, options)
+ ::Projects::MoveLfsObjectsProjectsService.new(@project, @current_user).execute(source_project, options)
+ add_source_project_to_fork_network(source_project)
+ end
+
+ def destroy_old_project(source_project)
+ # Delete previous project (synchronously) and unlink relations
+ ::Projects::DestroyService.new(source_project, @current_user).execute
+ end
+
+ def rename_project(name, path)
+ # Update de project's name and path to the original name/path
+ ::Projects::UpdateService.new(@project,
+ @current_user,
+ { name: name, path: path })
+ .execute
+ end
+
+ def attempt_restore_repositories(project)
+ ::Projects::DestroyService.new(project, @current_user).attempt_repositories_rollback
+ end
+
+ def add_source_project_to_fork_network(source_project)
+ return unless @project.fork_network
+
+ # Because he have moved all references in the fork network from the source_project
+ # we won't be able to query the database (only through its cached data),
+ # for its former relationships. That's why we're adding it to the network
+ # as a fork of the target project
+ ForkNetworkMember.create!(fork_network: @project.fork_network,
+ project: source_project,
+ forked_from_project: @project)
+ end
+ end
+end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 26765e5c3f3..5a23f0f0a62 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -24,6 +24,8 @@ module Projects
transfer(project)
+ current_user.invalidate_personal_projects_count
+
true
rescue Projects::TransferService::TransferError => ex
project.reload
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 5bf8208e035..de77f6bf585 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -31,15 +31,17 @@ module Projects
# Check if we did extract public directory
archive_public_path = File.join(archive_path, 'public')
- raise FailedToExtractError, 'pages miss the public folder' unless Dir.exist?(archive_public_path)
+ raise InvaildStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise InvaildStateError, 'pages are outdated' unless latest?
deploy_page!(archive_public_path)
success
end
- rescue InvaildStateError, FailedToExtractError => e
- register_failure
+ rescue InvaildStateError => e
error(e.message)
+ rescue => e
+ error(e.message, false)
+ raise e
end
private
@@ -50,12 +52,13 @@ module Projects
super
end
- def error(message, http_status = nil)
+ def error(message, allow_delete_artifact = true)
+ register_failure
log_error("Projects::UpdatePagesService: #{message}")
@status.allow_failure = !latest?
@status.description = message
@status.drop(:script_failure)
- delete_artifact!
+ delete_artifact! if allow_delete_artifact
super
end
@@ -71,33 +74,21 @@ module Projects
end
def extract_archive!(temp_path)
- if artifacts.ends_with?('.tar.gz') || artifacts.ends_with?('.tgz')
- extract_tar_archive!(temp_path)
- elsif artifacts.ends_with?('.zip')
+ if artifacts.ends_with?('.zip')
extract_zip_archive!(temp_path)
else
- raise FailedToExtractError, 'unsupported artifacts format'
- end
- end
-
- def extract_tar_archive!(temp_path)
- build.artifacts_file.use_file do |artifacts_path|
- results = Open3.pipeline(%W(gunzip -c #{artifacts_path}),
- %W(dd bs=#{BLOCK_SIZE} count=#{blocks}),
- %W(tar -x -C #{temp_path} #{SITE_PATH}),
- err: '/dev/null')
- raise FailedToExtractError, 'pages failed to extract' unless results.compact.all?(&:success?)
+ raise InvaildStateError, 'unsupported artifacts format'
end
end
def extract_zip_archive!(temp_path)
- raise FailedToExtractError, 'missing artifacts metadata' unless build.artifacts_metadata?
+ raise InvaildStateError, 'missing artifacts metadata' unless build.artifacts_metadata?
# Calculate page size after extract
public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true)
if public_entry.total_size > max_size
- raise FailedToExtractError, "artifacts for pages are too large: #{public_entry.total_size}"
+ raise InvaildStateError, "artifacts for pages are too large: #{public_entry.total_size}"
end
# Requires UnZip at least 6.00 Info-ZIP.
@@ -178,6 +169,9 @@ module Projects
def latest_sha
project.commit(build.ref).try(:sha).to_s
+ ensure
+ # Close any file descriptors that were opened and free libgit2 buffers
+ project.cleanup
end
def sha
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index cba49faac31..6cc51b6ee1b 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -200,7 +200,7 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
- available_labels = LabelsFinder.new(current_user, project_id: project.id).execute
+ available_labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true).execute
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
available_labels.any?
@@ -562,7 +562,7 @@ module QuickActions
def find_labels(labels_param)
extract_references(labels_param, :label) |
- LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute
+ LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split, include_ancestor_groups: true).execute
end
def find_label_references(labels_param)
@@ -593,6 +593,7 @@ module QuickActions
def extract_references(arg, type)
ext = Gitlab::ReferenceExtractor.new(project, current_user)
+
ext.analyze(arg, author: current_user)
ext.references(type)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 2253d638e93..00bf5434b7f 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -429,7 +429,7 @@ module SystemNoteService
def cross_reference(noteable, mentioner, author)
return if cross_reference_disallowed?(noteable, mentioner)
- gfm_reference = mentioner.gfm_reference(noteable.project)
+ gfm_reference = mentioner.gfm_reference(noteable.project || noteable.group)
body = cross_reference_note_content(gfm_reference)
if noteable.is_a?(ExternalIssue)
@@ -582,7 +582,7 @@ module SystemNoteService
text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}"
notes.where('(note LIKE ? OR note LIKE ?)', text, text.capitalize)
else
- gfm_reference = mentioner.gfm_reference(noteable.project)
+ gfm_reference = mentioner.gfm_reference(noteable.project || noteable.group)
text = cross_reference_note_content(gfm_reference)
notes.where(note: [text, text.capitalize])
end