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

branch_push_service.rb « git « services « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: b55aeb5f2b91743200c8049b1bc6b960bf71ccff (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# frozen_string_literal: true

module Git
  class BranchPushService < BaseService
    attr_accessor :push_data, :push_commits
    include Gitlab::Access
    include Gitlab::Utils::StrongMemoize

    # The N most recent commits to process in a single push payload.
    PROCESS_COMMIT_LIMIT = 100

    # This method will be called after each git update
    # and only if the provided user and project are present in GitLab.
    #
    # All callbacks for post receive action should be placed here.
    #
    # Next, this method:
    #  1. Creates the push event
    #  2. Updates merge requests
    #  3. Recognizes cross-references from commit messages
    #  4. Executes the project's webhooks
    #  5. Executes the project's services
    #  6. Checks if the project's main language has changed
    #
    def execute
      update_commits
      execute_related_hooks
      perform_housekeeping

      update_remote_mirrors
      update_caches

      update_signatures
    end

    def update_commits
      project.repository.after_create if project.empty_repo?
      project.repository.after_push_commit(branch_name)

      if push_remove_branch?
        project.repository.after_remove_branch
        @push_commits = []
      elsif push_to_new_branch?
        project.repository.after_create_branch

        # Re-find the pushed commits.
        if default_branch?
          # Initial push to the default branch. Take the full history of that branch as "newly pushed".
          process_default_branch
        else
          # Use the pushed commits that aren't reachable by the default branch
          # as a heuristic. This may include more commits than are actually pushed, but
          # that shouldn't matter because we check for existing cross-references later.
          @push_commits = project.repository.commits_between(project.default_branch, params[:newrev])

          # don't process commits for the initial push to the default branch
          process_commit_messages
        end
      elsif push_to_existing_branch?
        # Collect data for this git push
        @push_commits = project.repository.commits_between(params[:oldrev], params[:newrev])

        process_commit_messages

        # Update the bare repositories info/attributes file using the contents of the default branches
        # .gitattributes file
        update_gitattributes if default_branch?
      end
    end

    def update_gitattributes
      project.repository.copy_gitattributes(params[:ref])
    end

    def update_caches
      if default_branch?
        if push_to_new_branch?
          # If this is the initial push into the default branch, the file type caches
          # will already be reset as a result of `Project#change_head`.
          types = []
        else
          paths = Set.new

          last_pushed_commits.each do |commit|
            commit.raw_deltas.each do |diff|
              paths << diff.new_path
            end
          end

          types = Gitlab::FileDetector.types_in_paths(paths.to_a)
        end

        DetectRepositoryLanguagesWorker.perform_async(@project.id, current_user.id)
      else
        types = []
      end

      ProjectCacheWorker.perform_async(project.id, types, [:commit_count, :repository_size])
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def update_signatures
      commit_shas = last_pushed_commits.map(&:sha)

      return if commit_shas.empty?

      shas_with_cached_signatures = GpgSignature.where(commit_sha: commit_shas).pluck(:commit_sha)
      commit_shas -= shas_with_cached_signatures

      return if commit_shas.empty?

      commit_shas = Gitlab::Git::Commit.shas_with_signatures(project.repository, commit_shas)

      CreateGpgSignatureWorker.perform_async(commit_shas, project.id)
    end
    # rubocop: enable CodeReuse/ActiveRecord

    # Schedules processing of commit messages.
    def process_commit_messages
      default = default_branch?

      last_pushed_commits.each do |commit|
        if commit.matches_cross_reference_regex?
          ProcessCommitWorker
            .perform_async(project.id, current_user.id, commit.to_hash, default)
        end
      end
    end

    def update_remote_mirrors
      return unless project.has_remote_mirror?

      project.mark_stuck_remote_mirrors_as_failed!
      project.update_remote_mirrors
    end

    def execute_related_hooks
      # Update merge requests that may be affected by this push. A new branch
      # could cause the last commit of a merge request to change.
      #
      UpdateMergeRequestsWorker
        .perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])

      EventCreateService.new.push(project, current_user, build_push_data)
      Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push, pipeline_options)

      project.execute_hooks(build_push_data.dup, :push_hooks)
      project.execute_services(build_push_data.dup, :push_hooks)

      if push_remove_branch?
        AfterBranchDeleteService
          .new(project, current_user)
          .execute(branch_name)
      end
    end

    def perform_housekeeping
      housekeeping = Projects::HousekeepingService.new(project)
      housekeeping.increment!
      housekeeping.execute if housekeeping.needed?
    rescue Projects::HousekeepingService::LeaseTaken
    end

    def process_default_branch
      offset = [push_commits_count_for_ref - PROCESS_COMMIT_LIMIT, 0].max
      @push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)

      project.after_create_default_branch
    end

    def build_push_data
      @push_data ||= Gitlab::DataBuilder::Push.build(
        project,
        current_user,
        params[:oldrev],
        params[:newrev],
        params[:ref],
        @push_commits,
        commits_count: commits_count,
        push_options: params[:push_options] || []
      )
    end

    def push_to_existing_branch?
      # Return if this is not a push to a branch (e.g. new commits)
      branch_ref? && !Gitlab::Git.blank_ref?(params[:oldrev])
    end

    def push_to_new_branch?
      strong_memoize(:push_to_new_branch) do
        branch_ref? && Gitlab::Git.blank_ref?(params[:oldrev])
      end
    end

    def push_remove_branch?
      strong_memoize(:push_remove_branch) do
        branch_ref? && Gitlab::Git.blank_ref?(params[:newrev])
      end
    end

    def default_branch?
      branch_ref? &&
        (branch_name == project.default_branch || project.default_branch.nil?)
    end

    def commit_user(commit)
      commit.author || current_user
    end

    def branch_name
      strong_memoize(:branch_name) do
        Gitlab::Git.ref_name(params[:ref])
      end
    end

    def branch_ref?
      strong_memoize(:branch_ref) do
        Gitlab::Git.branch_ref?(params[:ref])
      end
    end

    def commits_count
      return push_commits_count_for_ref if default_branch? && push_to_new_branch?

      Array(@push_commits).size
    end

    def push_commits_count_for_ref
      strong_memoize(:push_commits_count_for_ref) do
        project.repository.commit_count_for_ref(params[:ref])
      end
    end

    def last_pushed_commits
      @last_pushed_commits ||= @push_commits.last(PROCESS_COMMIT_LIMIT)
    end

    private

    def pipeline_options
      {} # to be overridden in EE
    end
  end
end