summaryrefslogtreecommitdiff
path: root/app/services
diff options
context:
space:
mode:
Diffstat (limited to 'app/services')
-rw-r--r--app/services/boards/create_service.rb2
-rw-r--r--app/services/boards/destroy_service.rb11
-rw-r--r--app/services/boards/issues/move_service.rb6
-rw-r--r--app/services/boards/update_service.rb9
-rw-r--r--app/services/boards/visits/latest_service.rb19
-rw-r--r--app/services/branches/diverging_commit_counts_service.rb54
-rw-r--r--app/services/ci/pipeline_schedule_service.rb13
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb3
-rw-r--r--app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb19
-rw-r--r--app/services/files/create_service.rb2
-rw-r--r--app/services/files/multi_service.rb2
-rw-r--r--app/services/git/branch_hooks_service.rb14
-rw-r--r--app/services/git/wiki_push_service.rb9
-rw-r--r--app/services/issuable_base_service.rb13
-rw-r--r--app/services/issues/reorder_service.rb48
-rw-r--r--app/services/issues/update_service.rb1
-rw-r--r--app/services/lfs/file_transformer.rb26
-rw-r--r--app/services/merge_requests/create_from_issue_service.rb36
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb20
-rw-r--r--app/services/merge_requests/mergeability_check_service.rb120
-rw-r--r--app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb12
-rw-r--r--app/services/projects/propagate_service_template.rb2
-rw-r--r--app/services/search_service.rb4
-rw-r--r--app/services/service_response.rb15
-rw-r--r--app/services/system_note_service.rb7
-rw-r--r--app/services/users/build_service.rb10
-rw-r--r--app/services/users/update_service.rb25
27 files changed, 412 insertions, 90 deletions
diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index 1b796cef3e2..dd9358913fd 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -9,7 +9,7 @@ module Boards
private
def can_create_board?
- parent.boards.empty?
+ parent.boards.empty? || parent.multiple_issue_boards_available?
end
def create_board!
diff --git a/app/services/boards/destroy_service.rb b/app/services/boards/destroy_service.rb
new file mode 100644
index 00000000000..ea0c1394aa3
--- /dev/null
+++ b/app/services/boards/destroy_service.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Boards
+ class DestroyService < Boards::BaseService
+ def execute(board)
+ return false if parent.boards.size == 1
+
+ board.destroy
+ end
+ end
+end
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 834baeb9643..e27d34dbcab 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -79,9 +79,11 @@ module Boards
# rubocop: enable CodeReuse/ActiveRecord
def move_between_ids
- return unless params[:move_after_id] || params[:move_before_id]
+ ids = [params[:move_after_id], params[:move_before_id]]
+ .map(&:to_i)
+ .map { |m| m.positive? ? m : nil }
- [params[:move_after_id], params[:move_before_id]]
+ ids.any? ? ids : nil
end
end
end
diff --git a/app/services/boards/update_service.rb b/app/services/boards/update_service.rb
new file mode 100644
index 00000000000..88aced01ccd
--- /dev/null
+++ b/app/services/boards/update_service.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Boards
+ class UpdateService < Boards::BaseService
+ def execute(board)
+ board.update(params)
+ end
+ end
+end
diff --git a/app/services/boards/visits/latest_service.rb b/app/services/boards/visits/latest_service.rb
deleted file mode 100644
index d13e25b4f12..00000000000
--- a/app/services/boards/visits/latest_service.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-module Boards
- module Visits
- class LatestService < Boards::BaseService
- def execute
- return unless current_user
-
- recent_visit_model.latest(current_user, parent, count: params[:count])
- end
-
- private
-
- def recent_visit_model
- parent.is_a?(Group) ? BoardGroupRecentVisit : BoardProjectRecentVisit
- end
- end
- end
-end
diff --git a/app/services/branches/diverging_commit_counts_service.rb b/app/services/branches/diverging_commit_counts_service.rb
new file mode 100644
index 00000000000..f947cec1663
--- /dev/null
+++ b/app/services/branches/diverging_commit_counts_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Branches
+ class DivergingCommitCountsService
+ def initialize(repository)
+ @repository = repository
+ @cache = Gitlab::RepositoryCache.new(repository)
+ end
+
+ def call(branch)
+ if Feature.enabled?('gitaly_count_diverging_commits_no_max')
+ diverging_commit_counts_without_max(branch)
+ else
+ diverging_commit_counts(branch)
+ end
+ end
+
+ private
+
+ attr_reader :repository, :cache
+
+ delegate :raw_repository, to: :repository
+
+ def diverging_commit_counts(branch)
+ ## TODO: deprecate the below code after 12.0
+ @root_ref_hash ||= raw_repository.commit(repository.root_ref).id
+ cache.fetch(:"diverging_commit_counts_#{branch.name}") do
+ number_commits_behind, number_commits_ahead =
+ repository.raw_repository.diverging_commit_count(
+ @root_ref_hash,
+ branch.dereferenced_target.sha,
+ max_count: Repository::MAX_DIVERGING_COUNT)
+
+ if number_commits_behind + number_commits_ahead >= Repository::MAX_DIVERGING_COUNT
+ { distance: Repository::MAX_DIVERGING_COUNT }
+ else
+ { behind: number_commits_behind, ahead: number_commits_ahead }
+ end
+ end
+ end
+
+ def diverging_commit_counts_without_max(branch)
+ @root_ref_hash ||= raw_repository.commit(repository.root_ref).id
+ cache.fetch(:"diverging_commit_counts_without_max_#{branch.name}") do
+ number_commits_behind, number_commits_ahead =
+ raw_repository.diverging_commit_count(
+ @root_ref_hash,
+ branch.dereferenced_target.sha)
+
+ { behind: number_commits_behind, ahead: number_commits_ahead }
+ end
+ end
+ end
+end
diff --git a/app/services/ci/pipeline_schedule_service.rb b/app/services/ci/pipeline_schedule_service.rb
index 5b5e9a26520..ef90d91c936 100644
--- a/app/services/ci/pipeline_schedule_service.rb
+++ b/app/services/ci/pipeline_schedule_service.rb
@@ -7,7 +7,18 @@ module Ci
# Otherwise, multiple pipelines could be created in a short interval.
schedule.schedule_next_run!
- RunPipelineScheduleWorker.perform_async(schedule.id, schedule.owner&.id)
+ if Feature.enabled?(:ci_pipeline_schedule_async)
+ RunPipelineScheduleWorker.perform_async(schedule.id, schedule.owner&.id)
+ else
+ begin
+ RunPipelineScheduleWorker.new.perform(schedule.id, schedule.owner&.id)
+ ensure
+ ##
+ # This is the temporary solution for avoiding the memory bloat.
+ # See more https://gitlab.com/gitlab-org/gitlab-ce/issues/61955
+ GC.start if Feature.enabled?(:ci_pipeline_schedule_force_gc, default_enabled: true)
+ end
+ end
end
end
end
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 5525c1b9b7f..2f3c1df7651 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -12,9 +12,6 @@ module Clusters
create_gitlab_service_account!
configure_kubernetes
cluster.save!
-
- ClusterConfigureWorker.perform_async(cluster.id)
-
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
log_service_error(e.class.name, provider.id, e.message)
provider.make_errored!(s_('ClusterIntegration|Failed to request to Google Cloud Platform: %{message}') % { message: e.message })
diff --git a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
index 4ad04ab801e..5d9bdc52d37 100644
--- a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
+++ b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
@@ -4,17 +4,30 @@ module Clusters
module Gcp
module Kubernetes
class FetchKubernetesTokenService
+ DEFAULT_TOKEN_RETRY_DELAY = 5.seconds
+ TOKEN_RETRY_LIMIT = 5
+
attr_reader :kubeclient, :service_account_token_name, :namespace
- def initialize(kubeclient, service_account_token_name, namespace)
+ def initialize(kubeclient, service_account_token_name, namespace, token_retry_delay: DEFAULT_TOKEN_RETRY_DELAY)
@kubeclient = kubeclient
@service_account_token_name = service_account_token_name
@namespace = namespace
+ @token_retry_delay = token_retry_delay
end
def execute
- token_base64 = get_secret&.dig('data', 'token')
- Base64.decode64(token_base64) if token_base64
+ # Kubernetes will create the Secret and set the token asynchronously
+ # so it is necessary to retry
+ # https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#token-controller
+ TOKEN_RETRY_LIMIT.times do
+ token_base64 = get_secret&.dig('data', 'token')
+ return Base64.decode64(token_base64) if token_base64
+
+ sleep @token_retry_delay
+ end
+
+ nil
end
private
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index fd5442a6c28..f2cd51ef4d0 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -3,7 +3,7 @@
module Files
class CreateService < Files::BaseService
def create_commit!
- transformer = Lfs::FileTransformer.new(project, @branch_name)
+ transformer = Lfs::FileTransformer.new(project, repository, @branch_name)
result = transformer.new_file(@file_path, @file_content)
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index c1bc26c330a..d8c4e5bc5e8 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -5,7 +5,7 @@ module Files
UPDATE_FILE_ACTIONS = %w(update move delete chmod).freeze
def create_commit!
- transformer = Lfs::FileTransformer.new(project, @branch_name)
+ transformer = Lfs::FileTransformer.new(project, repository, @branch_name)
actions = actions_after_lfs_transformation(transformer, params[:actions])
actions = transform_move_actions(actions)
diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb
index d21a6bb1b9a..c41f445c3c4 100644
--- a/app/services/git/branch_hooks_service.rb
+++ b/app/services/git/branch_hooks_service.rb
@@ -20,8 +20,7 @@ module Git
strong_memoize(:commits) do
if creating_default_branch?
# The most recent PROCESS_COMMIT_LIMIT commits in the default branch
- offset = [count_commits_in_branch - PROCESS_COMMIT_LIMIT, 0].max
- project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
+ project.repository.commits(params[:newrev], limit: PROCESS_COMMIT_LIMIT)
elsif creating_branch?
# Use the pushed commits that aren't reachable by the default branch
# as a heuristic. This may include more commits than are actually
@@ -84,9 +83,6 @@ module Git
# Schedules processing of commit messages
def enqueue_process_commit_messages
- # don't process commits for the initial push to the default branch
- return if creating_default_branch?
-
limited_commits.each do |commit|
next unless commit.matches_cross_reference_regex?
@@ -109,8 +105,14 @@ module Git
CreateGpgSignatureWorker.perform_async(signable, project.id)
end
+ # It's not sufficient to just check for a blank SHA as it's possible for the
+ # branch to be pushed, but for the `post-receive` hook to never run:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/59257
def creating_branch?
- Gitlab::Git.blank_ref?(params[:oldrev])
+ strong_memoize(:creating_branch) do
+ Gitlab::Git.blank_ref?(params[:oldrev]) ||
+ !project.repository.branch_exists?(branch_name)
+ end
end
def updating_branch?
diff --git a/app/services/git/wiki_push_service.rb b/app/services/git/wiki_push_service.rb
new file mode 100644
index 00000000000..a053f133016
--- /dev/null
+++ b/app/services/git/wiki_push_service.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Git
+ class WikiPushService < ::BaseService
+ def execute
+ # This is used in EE
+ end
+ end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 26132f1824a..02de080e0ba 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -205,7 +205,7 @@ class IssuableBaseService < BaseService
end
if issuable.changed? || params.present?
- issuable.assign_attributes(params.merge(updated_by: current_user))
+ issuable.assign_attributes(params)
if has_title_or_description_changed?(issuable)
issuable.assign_attributes(last_edited_at: Time.now, last_edited_by: current_user)
@@ -213,11 +213,16 @@ class IssuableBaseService < BaseService
before_update(issuable)
+ # Do not touch when saving the issuable if only changes position within a list. We should call
+ # this method at this point to capture all possible changes.
+ should_touch = update_timestamp?(issuable)
+
+ issuable.updated_by = current_user if should_touch
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
update_project_counters = issuable.project && update_project_counter_caches?(issuable)
- if issuable.with_transaction_returning_status { issuable.save }
+ if issuable.with_transaction_returning_status { issuable.save(touch: should_touch) }
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels])
@@ -402,4 +407,8 @@ class IssuableBaseService < BaseService
def ensure_milestone_available(issuable)
issuable.milestone_id = nil unless issuable.milestone_available?
end
+
+ def update_timestamp?(issuable)
+ issuable.changes.keys != ["relative_position"]
+ end
end
diff --git a/app/services/issues/reorder_service.rb b/app/services/issues/reorder_service.rb
new file mode 100644
index 00000000000..02c18d31b5e
--- /dev/null
+++ b/app/services/issues/reorder_service.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Issues
+ class ReorderService < Issues::BaseService
+ def execute(issue)
+ return false unless can?(current_user, :update_issue, issue)
+ return false if group && !can?(current_user, :read_group, group)
+
+ attrs = issue_params(group)
+ return false if attrs.empty?
+
+ update(issue, attrs)
+ end
+
+ private
+
+ def group
+ return unless params[:group_full_path]
+
+ @group ||= Group.find_by_full_path(params[:group_full_path])
+ end
+
+ def update(issue, attrs)
+ ::Issues::UpdateService.new(project, current_user, attrs).execute(issue)
+ rescue ActiveRecord::RecordNotFound
+ false
+ end
+
+ def issue_params(group)
+ attrs = {}
+
+ if move_between_ids
+ attrs[:move_between_ids] = move_between_ids
+ attrs[:board_group_id] = group&.id
+ end
+
+ attrs
+ end
+
+ def move_between_ids
+ ids = [params[:move_after_id], params[:move_before_id]]
+ .map(&:to_i)
+ .map { |m| m.positive? ? m : nil }
+
+ ids.any? ? ids : nil
+ end
+ end
+end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index cb2337d29d4..6b9f23f24cd 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -76,6 +76,7 @@ module Issues
issue_before = get_issue_if_allowed(before_id, board_group_id)
issue_after = get_issue_if_allowed(after_id, board_group_id)
+ raise ActiveRecord::RecordNotFound unless issue_before || issue_after
issue.move_between(issue_before, issue_after)
end
diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb
index 5239fe1b6e3..88f59b820a4 100644
--- a/app/services/lfs/file_transformer.rb
+++ b/app/services/lfs/file_transformer.rb
@@ -4,21 +4,29 @@ module Lfs
# Usage: Calling `new_file` check to see if a file should be in LFS and
# return a transformed result with `content` and `encoding` to commit.
#
+ # The `repository` passed to the initializer can be a Repository or
+ # a DesignManagement::Repository (an EE-specific class that inherits
+ # from Repository).
+ #
+ # The `repository_type` property will be one of the types named in
+ # `Gitlab::GlRepository.types`, and is recorded on the `LfsObjectsProject`
+ # in order to identify the repository location of the blob.
+ #
# For LFS an LfsObject linked to the project is stored and an LFS
# pointer returned. If the file isn't in LFS the untransformed content
# is returned to save in the commit.
#
- # transformer = Lfs::FileTransformer.new(project, @branch_name)
+ # transformer = Lfs::FileTransformer.new(project, repository, @branch_name)
# content_or_lfs_pointer = transformer.new_file(file_path, content).content
# create_transformed_commit(content_or_lfs_pointer)
#
class FileTransformer
- attr_reader :project, :branch_name
-
- delegate :repository, to: :project
+ attr_reader :project, :repository, :repository_type, :branch_name
- def initialize(project, branch_name)
+ def initialize(project, repository, branch_name)
@project = project
+ @repository = repository
+ @repository_type = repository.repo_type.name
@branch_name = branch_name
end
@@ -52,7 +60,7 @@ module Lfs
end
def cached_attributes
- @cached_attributes ||= Gitlab::Git::AttributesAtRefParser.new(repository, branch_name)
+ @cached_attributes ||= repository.attributes_at(branch_name)
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -64,7 +72,11 @@ module Lfs
# rubocop: enable CodeReuse/ActiveRecord
def link_lfs_object!(lfs_object)
- project.lfs_objects << lfs_object
+ LfsObjectsProject.safe_find_or_create_by!(
+ project: project,
+ lfs_object: lfs_object,
+ repository_type: repository_type
+ )
end
def parse_file_content(file_content, encoding: nil)
diff --git a/app/services/merge_requests/create_from_issue_service.rb b/app/services/merge_requests/create_from_issue_service.rb
index e69791872cc..2a217a6f689 100644
--- a/app/services/merge_requests/create_from_issue_service.rb
+++ b/app/services/merge_requests/create_from_issue_service.rb
@@ -6,17 +6,20 @@ module MergeRequests
# branch - the name of new branch
# ref - the source of new branch.
- @branch_name = params[:branch_name]
- @issue_iid = params[:issue_iid]
- @ref = params[:ref]
+ @branch_name = params[:branch_name]
+ @issue_iid = params[:issue_iid]
+ @ref = params[:ref]
+ @target_project_id = params[:target_project_id]
super(project, user)
end
def execute
+ return error('Project not found') if target_project.blank?
+ return error('Not allowed to create merge request') unless can_create_merge_request?
return error('Invalid issue iid') unless @issue_iid.present? && issue.present?
- result = CreateBranchService.new(project, current_user).execute(branch_name, ref)
+ result = CreateBranchService.new(target_project, current_user).execute(branch_name, ref)
return result if result[:status] == :error
new_merge_request = create(merge_request)
@@ -26,7 +29,7 @@ module MergeRequests
success(new_merge_request)
else
- SystemNoteService.new_issue_branch(issue, project, current_user, branch_name)
+ SystemNoteService.new_issue_branch(issue, project, current_user, branch_name, branch_project: target_project)
error(new_merge_request.errors)
end
@@ -34,6 +37,10 @@ module MergeRequests
private
+ def can_create_merge_request?
+ can?(current_user, :create_merge_request_from, target_project)
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def issue
@issue ||= IssuesFinder.new(current_user, project_id: project.id).find_by(iid: @issue_iid)
@@ -45,21 +52,21 @@ module MergeRequests
end
def ref
- return @ref if project.repository.branch_exists?(@ref)
+ return @ref if target_project.repository.branch_exists?(@ref)
- project.default_branch || 'master'
+ target_project.default_branch || 'master'
end
def merge_request
- MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
+ MergeRequests::BuildService.new(target_project, current_user, merge_request_params).execute
end
def merge_request_params
{
issue_iid: @issue_iid,
- source_project_id: project.id,
+ source_project_id: target_project.id,
source_branch: branch_name,
- target_project_id: project.id,
+ target_project_id: target_project.id,
target_branch: ref
}
end
@@ -67,5 +74,14 @@ module MergeRequests
def success(merge_request)
super().merge(merge_request: merge_request)
end
+
+ def target_project
+ @target_project ||=
+ if @target_project_id.present?
+ project.forks.find_by_id(@target_project_id)
+ else
+ project
+ end
+ end
end
end
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
index 87147d90c32..efe4dcd6255 100644
--- a/app/services/merge_requests/merge_to_ref_service.rb
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -11,6 +11,8 @@ module MergeRequests
# be executed regardless of the `target_ref` current state).
#
class MergeToRefService < MergeRequests::MergeBaseService
+ extend ::Gitlab::Utils::Override
+
def execute(merge_request)
@merge_request = merge_request
@@ -26,14 +28,18 @@ module MergeRequests
success(commit_id: commit.id,
target_id: target_id,
source_id: source_id)
- rescue MergeError => error
+ rescue MergeError, ArgumentError => error
error(error.message)
end
private
+ override :source
+ def source
+ merge_request.diff_head_sha
+ end
+
def validate!
- authorization_check!
error_check!
end
@@ -43,21 +49,13 @@ module MergeRequests
error =
if !hooks_validation_pass?(merge_request)
hooks_validation_error(merge_request)
- elsif !@merge_request.mergeable_to_ref?
- "Merge request is not mergeable to #{target_ref}"
- elsif !source
+ elsif source.blank?
'No source for merge'
end
raise_error(error) if error
end
- def authorization_check!
- unless Ability.allowed?(current_user, :admin_merge_request, project)
- raise_error("You are not allowed to merge to this ref")
- end
- end
-
def target_ref
merge_request.merge_ref_path
end
diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb
new file mode 100644
index 00000000000..9fa50c9448f
--- /dev/null
+++ b/app/services/merge_requests/mergeability_check_service.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MergeabilityCheckService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ delegate :project, to: :@merge_request
+ delegate :repository, to: :project
+
+ def initialize(merge_request)
+ @merge_request = merge_request
+ end
+
+ # Updates the MR merge_status. Whenever it switches to a can_be_merged state,
+ # the merge-ref is refreshed.
+ #
+ # recheck - When given, it'll enforce a merge-ref refresh if the current merge_status is
+ # can_be_merged or cannot_be_merged and merge-ref is outdated.
+ # Given MergeRequests::RefreshService is called async, it might happen that the target
+ # branch gets updated, but the MergeRequest#merge_status lags behind. So in scenarios
+ # where we need the current state of the merge ref in repository, the `recheck`
+ # argument is required.
+ #
+ # Returns a ServiceResponse indicating merge_status is/became can_be_merged
+ # and the merge-ref is synced. Success in case of being/becoming mergeable,
+ # error otherwise.
+ def execute(recheck: false)
+ return ServiceResponse.error(message: 'Invalid argument') unless merge_request
+ return ServiceResponse.error(message: 'Unsupported operation') if Gitlab::Database.read_only?
+
+ recheck! if recheck
+ update_merge_status
+
+ unless merge_request.can_be_merged?
+ return ServiceResponse.error(message: 'Merge request is not mergeable')
+ end
+
+ unless merge_ref_auto_sync_enabled?
+ return ServiceResponse.error(message: 'Merge ref is outdated due to disabled feature')
+ end
+
+ unless payload.fetch(:merge_ref_head)
+ return ServiceResponse.error(message: 'Merge ref cannot be updated')
+ end
+
+ ServiceResponse.success(payload: payload)
+ end
+
+ private
+
+ attr_reader :merge_request
+
+ def payload
+ strong_memoize(:payload) do
+ {
+ merge_ref_head: merge_ref_head_payload
+ }
+ end
+ end
+
+ def merge_ref_head_payload
+ commit = merge_request.merge_ref_head
+
+ return unless commit
+
+ target_id, source_id = commit.parent_ids
+
+ {
+ commit_id: commit.id,
+ source_id: source_id,
+ target_id: target_id
+ }
+ end
+
+ def update_merge_status
+ return unless merge_request.recheck_merge_status?
+
+ if can_git_merge? && merge_to_ref
+ merge_request.mark_as_mergeable
+ else
+ merge_request.mark_as_unmergeable
+ end
+ end
+
+ def recheck!
+ if !merge_request.recheck_merge_status? && outdated_merge_ref?
+ merge_request.mark_as_unchecked
+ end
+ end
+
+ # Checks if the existing merge-ref is synced with the target branch.
+ #
+ # Returns true if the merge-ref does not exists or is out of sync.
+ def outdated_merge_ref?
+ return false unless merge_ref_auto_sync_enabled?
+ return false unless merge_request.open?
+
+ return true unless ref_head = merge_request.merge_ref_head
+ return true unless target_sha = merge_request.target_branch_sha
+ return true unless source_sha = merge_request.source_branch_sha
+
+ ref_head.parent_ids != [target_sha, source_sha]
+ end
+
+ def can_git_merge?
+ !merge_request.broken? && repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch)
+ end
+
+ def merge_to_ref
+ return true unless merge_ref_auto_sync_enabled?
+
+ result = MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ result[:status] == :success
+ end
+
+ def merge_ref_auto_sync_enabled?
+ Feature.enabled?(:merge_ref_auto_sync, project, default_enabled: true)
+ end
+ end
+end
diff --git a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
index 2dfe1a3d8ca..58f795e639e 100644
--- a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
+++ b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
@@ -2,6 +2,14 @@
module PagesDomains
class ObtainLetsEncryptCertificateService
+ # time for processing validation requests for acme challenges
+ # 5-15 seconds is usually enough
+ CHALLENGE_PROCESSING_DELAY = 1.minute.freeze
+
+ # time LetsEncrypt ACME server needs to generate the certificate
+ # no particular SLA, usually takes 10-15 seconds
+ CERTIFICATE_PROCESSING_DELAY = 1.minute.freeze
+
attr_reader :pages_domain
def initialize(pages_domain)
@@ -14,6 +22,7 @@ module PagesDomains
unless acme_order
::PagesDomains::CreateAcmeOrderService.new(pages_domain).execute
+ PagesDomainSslRenewalWorker.perform_in(CHALLENGE_PROCESSING_DELAY, pages_domain.id)
return
end
@@ -23,6 +32,7 @@ module PagesDomains
case api_order.status
when 'ready'
api_order.request_certificate(private_key: acme_order.private_key, domain: pages_domain.domain)
+ PagesDomainSslRenewalWorker.perform_in(CERTIFICATE_PROCESSING_DELAY, pages_domain.id)
when 'valid'
save_certificate(acme_order.private_key, api_order)
acme_order.destroy!
@@ -35,7 +45,7 @@ module PagesDomains
def save_certificate(private_key, api_order)
certificate = api_order.certificate
- pages_domain.update!(key: private_key, certificate: certificate)
+ pages_domain.update!(gitlab_provided_key: private_key, gitlab_provided_certificate: certificate)
end
end
end
diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb
index a2f36d2bd1b..a25c985585b 100644
--- a/app/services/projects/propagate_service_template.rb
+++ b/app/services/projects/propagate_service_template.rb
@@ -24,7 +24,7 @@ module Projects
def propagate_projects_with_template
loop do
- batch = project_ids_batch
+ batch = Project.uncached { project_ids_batch }
bulk_create_from_template(batch) unless batch.empty?
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 302510341ac..e0cbfac2420 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -52,10 +52,6 @@ class SearchService
@search_objects ||= search_results.objects(scope, params[:page])
end
- def display_options
- @display_options ||= search_results.display_options(scope)
- end
-
private
def search_service
diff --git a/app/services/service_response.rb b/app/services/service_response.rb
index 1de30e68d87..f3437ba16de 100644
--- a/app/services/service_response.rb
+++ b/app/services/service_response.rb
@@ -1,19 +1,20 @@
# frozen_string_literal: true
class ServiceResponse
- def self.success(message: nil)
- new(status: :success, message: message)
+ def self.success(message: nil, payload: {})
+ new(status: :success, message: message, payload: payload)
end
- def self.error(message:, http_status: nil)
- new(status: :error, message: message, http_status: http_status)
+ def self.error(message:, payload: {}, http_status: nil)
+ new(status: :error, message: message, payload: payload, http_status: http_status)
end
- attr_reader :status, :message, :http_status
+ attr_reader :status, :message, :http_status, :payload
- def initialize(status:, message: nil, http_status: nil)
+ def initialize(status:, message: nil, payload: {}, http_status: nil)
self.status = status
self.message = message
+ self.payload = payload
self.http_status = http_status
end
@@ -27,5 +28,5 @@ class ServiceResponse
private
- attr_writer :status, :message, :http_status
+ attr_writer :status, :message, :http_status, :payload
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 27d59279d75..237ddbcf2c2 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -404,8 +404,9 @@ module SystemNoteService
# Example note text:
#
# "created branch `201-issue-branch-button`"
- def new_issue_branch(issue, project, author, branch)
- link = url_helpers.project_compare_path(project, from: project.default_branch, to: branch)
+ def new_issue_branch(issue, project, author, branch, branch_project: nil)
+ branch_project ||= project
+ link = url_helpers.project_compare_path(branch_project, from: branch_project.default_branch, to: branch)
body = "created branch [`#{branch}`](#{link}) to address this issue"
@@ -413,7 +414,7 @@ module SystemNoteService
end
def new_merge_request(issue, project, author, merge_request)
- body = "created merge request #{merge_request.to_reference} to address this issue"
+ body = "created merge request #{merge_request.to_reference(project)} to address this issue"
create_note(NoteSummary.new(issue, project, author, body, action: 'merge'))
end
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index 30f7743c56e..026bcfcdaf4 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -5,10 +5,12 @@ module Users
delegate :user_default_internal_regex_enabled?,
:user_default_internal_regex_instance,
to: :'Gitlab::CurrentSettings.current_application_settings'
+ attr_reader :identity_params
def initialize(current_user, params = {})
@current_user = current_user
@params = params.dup
+ @identity_params = params.slice(*identity_attributes)
end
def execute(skip_authorization: false)
@@ -26,10 +28,8 @@ module Users
end
end
- identity_attrs = params.slice(*identity_params)
-
- unless identity_attrs.empty?
- user.identities.build(identity_attrs)
+ unless identity_params.empty?
+ user.identities.build(identity_params)
end
user
@@ -37,7 +37,7 @@ module Users
private
- def identity_params
+ def identity_attributes
[:extern_uid, :provider]
end
diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb
index 0b00bd135eb..8f52e9cb23f 100644
--- a/app/services/users/update_service.rb
+++ b/app/services/users/update_service.rb
@@ -3,11 +3,13 @@
module Users
class UpdateService < BaseService
include NewUserNotifier
+ attr_reader :user, :identity_params
def initialize(current_user, params = {})
@current_user = current_user
@user = params.delete(:user)
@status_params = params.delete(:status)
+ @identity_params = params.slice(*identity_attributes)
@params = params.dup
end
@@ -15,8 +17,8 @@ module Users
yield(@user) if block_given?
user_exists = @user.persisted?
-
assign_attributes
+ assign_identity
if @user.save(validate: validate) && update_status
notify_success(user_exists)
@@ -55,7 +57,26 @@ module Users
params.reject! { |key, _| read_only.include?(key.to_sym) }
end
- @user.assign_attributes(params) unless params.empty?
+ @user.assign_attributes(params.except(*identity_attributes)) unless params.empty? # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def assign_identity
+ return unless identity_params.present?
+
+ identity = user.identities.find_or_create_by(provider_params) # rubocop: disable CodeReuse/ActiveRecord
+ identity.update(identity_params)
+ end
+
+ def identity_attributes
+ [:provider, :extern_uid]
+ end
+
+ def provider_attributes
+ [:provider]
+ end
+
+ def provider_params
+ identity_params.slice(*provider_attributes)
end
end
end