Welcome to mirror list, hosted at ThFree Co, Russian Federation.

create_ref_service.rb « merge_requests « services « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: e0f10183bac9c7d7331aa2dbf9d69e77dfc4983a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# frozen_string_literal: true

module MergeRequests
  # CreateRefService creates or overwrites a ref under "refs/merge-requests/"
  # with a commit for the merged result.
  class CreateRefService
    include Gitlab::Utils::StrongMemoize

    CreateRefError = Class.new(StandardError)

    def initialize(
      current_user:, merge_request:, target_ref:, first_parent_ref:,
      source_sha: nil, merge_commit_message: nil)

      @current_user = current_user
      @merge_request = merge_request
      @initial_source_sha = source_sha
      @target_ref = target_ref
      @merge_commit_message = merge_commit_message
      @first_parent_sha = target_project.commit(first_parent_ref)&.sha
    end

    def execute
      commit_sha = initial_source_sha # the SHA to be at HEAD of target_ref
      source_sha = initial_source_sha # the SHA to be the merged result of the source (minus the merge commit)
      expected_old_oid = ""           # the SHA we expect target_ref to be at prior to an update (an optimistic lock)

      # TODO: Update this message with the removal of FF merge_trains_create_ref_service and update tests
      # This is for compatibility with MergeToRefService during the rollout.
      return ServiceResponse.error(message: '3:Invalid merge source') unless first_parent_sha.present?

      commit_sha, source_sha, expected_old_oid = maybe_squash!(commit_sha, source_sha, expected_old_oid)
      commit_sha, source_sha, expected_old_oid = maybe_rebase!(commit_sha, source_sha, expected_old_oid)
      commit_sha, source_sha = maybe_merge!(commit_sha, source_sha, expected_old_oid)

      ServiceResponse.success(
        payload: {
          commit_sha: commit_sha,
          target_sha: first_parent_sha,
          source_sha: source_sha
        }
      )
    rescue CreateRefError => error
      ServiceResponse.error(message: error.message)
    end

    private

    attr_reader :current_user, :merge_request, :target_ref, :first_parent_sha, :initial_source_sha

    delegate :target_project, to: :merge_request
    delegate :repository, to: :target_project

    def maybe_squash!(commit_sha, source_sha, expected_old_oid)
      if merge_request.squash_on_merge?
        squash_result = MergeRequests::SquashService.new(
          merge_request: merge_request,
          current_user: current_user,
          commit_message: squash_commit_message
        ).execute
        raise CreateRefError, squash_result[:message] if squash_result[:status] == :error

        commit_sha = squash_result[:squash_sha]
        source_sha = commit_sha
      end

      # squash does not overwrite target_ref, so expected_old_oid remains the same
      [commit_sha, source_sha, expected_old_oid]
    end

    def maybe_rebase!(commit_sha, source_sha, expected_old_oid)
      if target_project.ff_merge_must_be_possible?
        commit_sha = safe_gitaly_operation do
          repository.rebase_to_ref(
            current_user,
            source_sha: source_sha,
            target_ref: target_ref,
            first_parent_ref: first_parent_sha
          )
        end

        source_sha = commit_sha
        expected_old_oid = commit_sha
      end

      [commit_sha, source_sha, expected_old_oid]
    end

    def maybe_merge!(commit_sha, source_sha, expected_old_oid)
      unless target_project.merge_requests_ff_only_enabled
        commit_sha = safe_gitaly_operation do
          repository.merge_to_ref(
            current_user,
            source_sha: source_sha,
            target_ref: target_ref,
            message: merge_commit_message,
            first_parent_ref: first_parent_sha,
            branch: nil,
            expected_old_oid: expected_old_oid
          )
        end
        commit = target_project.commit(commit_sha)
        _, source_sha = commit.parent_ids
      end

      [commit_sha, source_sha]
    end

    def safe_gitaly_operation
      yield
    rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError, ArgumentError => error
      raise CreateRefError, error.message
    end

    def squash_commit_message
      merge_request.merge_params['squash_commit_message'].presence ||
        merge_request.default_squash_commit_message(user: current_user)
    end
    strong_memoize_attr :squash_commit_message

    def merge_commit_message
      return @merge_commit_message if @merge_commit_message.present?

      @merge_commit_message = (
        merge_request.merge_params['commit_message'].presence ||
        merge_request.default_merge_commit_message(user: current_user)
      )
    end
  end
end