1# frozen_string_literal: true 2 3module Gitlab 4 module Import 5 module MergeRequestHelpers 6 include DatabaseHelpers 7 8 # rubocop: disable CodeReuse/ActiveRecord 9 def create_merge_request_without_hooks(project, attributes, iid) 10 # This work must be wrapped in a transaction as otherwise we can leave 11 # behind incomplete data in the event of an error. This can then lead 12 # to duplicate key errors when jobs are retried. 13 MergeRequest.transaction do 14 # When creating merge requests there are a lot of hooks that may 15 # run, for many different reasons. Many of these hooks (e.g. the 16 # ones used for rendering Markdown) are completely unnecessary and 17 # may even lead to transaction timeouts. 18 # 19 # To ensure importing pull requests has a minimal impact and can 20 # complete in a reasonable time we bypass all the hooks by inserting 21 # the row and then retrieving it. We then only perform the 22 # additional work that is strictly necessary. 23 merge_request_id = insert_and_return_id(attributes, project.merge_requests) 24 25 merge_request = project.merge_requests.reset.find(merge_request_id) 26 27 [merge_request, false] 28 end 29 rescue ActiveRecord::InvalidForeignKey 30 # It's possible the project has been deleted since scheduling this 31 # job. In this case we'll just skip creating the merge request. 32 [] 33 rescue ActiveRecord::RecordNotUnique 34 # It's possible we previously created the MR, but failed when updating 35 # the Git data. In this case we'll just continue working on the 36 # existing row. 37 [project.merge_requests.find_by(iid: iid), true] 38 end 39 # rubocop: enable CodeReuse/ActiveRecord 40 41 def insert_or_replace_git_data(merge_request, source_branch_sha, target_branch_sha, already_exists = false) 42 # These fields are set so we can create the correct merge request 43 # diffs. 44 merge_request.source_branch_sha = source_branch_sha 45 merge_request.target_branch_sha = target_branch_sha 46 47 merge_request.keep_around_commit 48 49 # We force to recreate all diffs to replace all existing data 50 # We use `.all` as otherwise `dependent: :nullify` (the default) 51 # takes an effect 52 merge_request.merge_request_diffs.all.delete_all if already_exists 53 54 # MR diffs normally use an "after_save" hook to pull data from Git. 55 # All of this happens in the transaction started by calling 56 # create/save/etc. This in turn can lead to these transactions being 57 # held open for much longer than necessary. To work around this we 58 # first save the diff, then populate it. 59 diff = merge_request.merge_request_diffs.build 60 diff.importing = true 61 diff.save 62 diff.save_git_content 63 diff.set_as_latest_diff 64 end 65 end 66 end 67end 68