File: process_ref_changes_service.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (148 lines) | stat: -rw-r--r-- 4,856 bytes parent folder | download
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
# frozen_string_literal: true

module Git
  class ProcessRefChangesService < BaseService
    PIPELINE_PROCESS_LIMIT = 4

    def execute
      changes = params[:changes]

      process_changes_by_action(:branch, changes.branch_changes)
      process_changes_by_action(:tag, changes.tag_changes)
      warn_if_over_process_limit(changes.branch_changes + changes.tag_changes)

      perform_housekeeping
    end

    private

    def process_changes_by_action(ref_type, changes)
      changes_by_action = group_changes_by_action(changes)

      changes_by_action.each do |action, changes|
        process_changes(ref_type, action, changes, execute_project_hooks: execute_project_hooks?(changes)) if changes.any?
      end
    end

    def group_changes_by_action(changes)
      changes.group_by do |change|
        change_action(change)
      end
    end

    def change_action(change)
      return :created if Gitlab::Git.blank_ref?(change[:oldrev])
      return :removed if Gitlab::Git.blank_ref?(change[:newrev])

      :pushed
    end

    def execute_project_hooks?(changes)
      changes.size <= Gitlab::CurrentSettings.push_event_hooks_limit
    end

    def process_changes(ref_type, action, changes, execute_project_hooks:)
      push_service_class = push_service_class_for(ref_type)

      create_bulk_push_event = changes.size > Gitlab::CurrentSettings.push_event_activities_limit
      merge_request_branches = merge_request_branches_for(ref_type, changes)

      changes.each do |change|
        options = {
          change: change,
          push_options: params[:push_options],
          merge_request_branches: merge_request_branches,
          create_pipelines: under_process_limit?(change),
          execute_project_hooks: execute_project_hooks,
          create_push_event: !create_bulk_push_event
        }

        if ref_type == :branch && Feature.enabled?(:throttle_with_process_commit_worker_pool, project)
          options[:process_commit_worker_pool] = process_commit_worker_pool
        end

        push_service_class.new(
          project,
          current_user,
          **options
        ).execute
      end

      create_bulk_push_event(ref_type, action, changes) if create_bulk_push_event
    end

    def under_process_limit?(change)
      change[:index] < process_limit || Feature.enabled?(:git_push_create_all_pipelines, project)
    end

    def process_limit
      PIPELINE_PROCESS_LIMIT
    end

    def warn_if_over_process_limit(changes)
      return unless process_limit > 0
      return if changes.length <= process_limit

      # We don't know for sure whether the project has CI enabled or CI rules
      # that might excluded pipelines from being created.
      omitted_refs = possible_omitted_pipeline_refs(changes)

      return unless omitted_refs.present?

      # This notification only lets the admin know that we might have skipped some refs.
      Gitlab::AppJsonLogger.info(
        message: "Some pipelines may not have been created because ref count exceeded limit",
        ref_limit: process_limit,
        total_ref_count: changes.length,
        possible_omitted_refs: omitted_refs,
        possible_omitted_ref_count: omitted_refs.length,
        **Gitlab::ApplicationContext.current
      )
    end

    def possible_omitted_pipeline_refs(changes)
      # Pipelines can only be created on pushed for branch creation or updates
      omitted_changes = changes.select do |change|
        change[:index] >= process_limit &&
          change_action(change) != :removed
      end

      # rubocop:disable CodeReuse/ActiveRecord -- not an ActiveRecord model
      # rubocop:disable Database/AvoidUsingPluckWithoutLimit -- not an ActiveRecord model
      omitted_changes.pluck(:ref).sort
      # rubocop:enable CodeReuse/ActiveRecord
      # rubocop:enable Database/AvoidUsingPluckWithoutLimit
    end

    def create_bulk_push_event(ref_type, action, changes)
      EventCreateService.new.bulk_push(
        project,
        current_user,
        Gitlab::DataBuilder::Push.build_bulk(action: action, ref_type: ref_type, changes: changes)
      )
    end

    def push_service_class_for(ref_type)
      return Git::TagPushService if ref_type == :tag

      Git::BranchPushService
    end

    def merge_request_branches_for(ref_type, changes)
      return [] if ref_type == :tag

      MergeRequests::PushedBranchesService.new(project: project, current_user: current_user, params: { changes: changes }).execute
    end

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

    def process_commit_worker_pool
      @process_commit_worker_pool ||= Gitlab::Git::ProcessCommitWorkerPool.new
    end
  end
end