gitlab-org--gitlab-foss/app/services/deployments/link_merge_requests_service.rb

97 lines
3.7 KiB
Ruby

# frozen_string_literal: true
module Deployments
# Service class for linking merge requests to deployments.
class LinkMergeRequestsService
attr_reader :deployment
# The number of commits per query for which to find merge requests.
COMMITS_PER_QUERY = 5_000
def initialize(deployment)
@deployment = deployment
end
def execute
# Review apps have the environment type set (e.g. to `review`, though the
# exact value may differ). We don't want to link merge requests to review
# app deployments, as this is not useful.
return unless deployment.environment.should_link_to_merge_requests?
# This service is triggered by a Sidekiq worker, which only runs when a
# deployment is successful. We add an extra check here in case we ever
# call this service elsewhere and forget to check the status there.
#
# The reason we only want to link successful deployments is as follows:
# when we link a merge request, we don't link it to future deployments for
# the same environment. If we were to link an MR to a failed deploy, we
# wouldn't be able to later on link it to a successful deploy (e.g. after
# the deploy is retried).
#
# In addition, showing failed deploys in the UI of a merge request isn't
# useful to users, as they can't act upon the information in any
# meaningful way (i.e. they can't just retry the deploy themselves).
return unless deployment.success?
if (prev = deployment.previous_deployment)
link_merge_requests_for_range(prev.sha, deployment.sha)
else
# When no previous deployment is found we fall back to linking all merge
# requests merged into the deployed branch. This will not always be
# accurate, but it's better than having no data.
#
# We can't use the first commit in the repository as a base to compare
# to, as this will not scale to large repositories. For example, GitLab
# itself has over 150 000 commits.
link_all_merged_merge_requests
end
end
def link_merge_requests_for_range(from, to)
commits = project
.repository
.commits_between(from, to)
.map(&:id)
# For some projects the list of commits to deploy may be very large. To
# ensure we do not end up running SQL queries with thousands of WHERE IN
# values, we run one query per a certain number of commits.
#
# In most cases this translates to only a single query. For very large
# deployment we may end up running a handful of queries to get and insert
# the data.
commits.each_slice(COMMITS_PER_QUERY) do |slice|
merge_requests =
project.merge_requests.merged.by_merge_commit_sha(slice)
deployment.link_merge_requests(merge_requests)
# The cherry picked commits are tracked via `notes.commit_id`
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22209
#
# NOTE: cross-joining `merge_requests` table and `notes` table could
# result in very poor performance because PG planner often uses an
# inappropriate index.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/321032.
mr_ids = project.notes.cherry_picked_merge_requests(slice)
picked_merge_requests = project.merge_requests.id_in(mr_ids)
deployment.link_merge_requests(picked_merge_requests)
end
end
def link_all_merged_merge_requests
merge_requests =
project.merge_requests.merged.by_target_branch(deployment.ref)
deployment.link_merge_requests(merge_requests)
end
private
def project
deployment.project
end
end
end