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