summaryrefslogtreecommitdiff
path: root/app/services/git/base_hooks_service.rb
blob: 35a4d2015fa4b08d038f6d035d13ab6814b0535b (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
# frozen_string_literal: true

module Git
  class BaseHooksService < ::BaseService
    include Gitlab::Utils::StrongMemoize

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

    def execute
      create_events
      create_pipelines
      execute_project_hooks

      # Not a hook, but it needs access to the list of changed commits
      enqueue_invalidate_cache

      update_remote_mirrors

      success
    end

    private

    def hook_name
      raise NotImplementedError, "Please implement #{self.class}##{__method__}"
    end

    def commits
      raise NotImplementedError, "Please implement #{self.class}##{__method__}"
    end

    def limited_commits
      @limited_commits ||= commits.last(PROCESS_COMMIT_LIMIT)
    end

    def commits_count
      commits.count
    end

    def event_message
      nil
    end

    def invalidated_file_types
      []
    end

    # Push events in the activity feed only show information for the
    # last commit.
    def create_events
      EventCreateService.new.push(project, current_user, event_push_data)
    end

    def create_pipelines
      return unless params.fetch(:create_pipelines, true)

      Ci::CreatePipelineService
        .new(project, current_user, pipeline_params)
        .execute!(:push, pipeline_options)
    rescue Ci::CreatePipelineService::CreateError => ex
      log_pipeline_errors(ex)
    end

    def execute_project_hooks
      # Creating push_data invokes one CommitDelta RPC per commit. Only
      # build this data if we actually need it.
      project.execute_hooks(push_data, hook_name) if project.has_active_hooks?(hook_name)
      project.execute_services(push_data, hook_name) if project.has_active_services?(hook_name)
    end

    def enqueue_invalidate_cache
      file_types = invalidated_file_types

      return unless file_types.present?

      ProjectCacheWorker.perform_async(project.id, file_types, [], false)
    end

    def pipeline_params
      {
        before: params[:oldrev],
        after: params[:newrev],
        ref: params[:ref],
        push_options: params[:push_options] || {},
        checkout_sha: Gitlab::DataBuilder::Push.checkout_sha(
          project.repository, params[:newrev], params[:ref])
      }
    end

    def push_data_params(commits:, with_changed_files: true)
      {
        oldrev: params[:oldrev],
        newrev: params[:newrev],
        ref: params[:ref],
        project: project,
        user: current_user,
        commits: commits,
        message: event_message,
        commits_count: commits_count,
        with_changed_files: with_changed_files
      }
    end

    def event_push_data
      # We only need the last commit for the event push, and we don't
      # need the full deltas either.
      @event_push_data ||= Gitlab::DataBuilder::Push.build(
        push_data_params(commits: commits.last, with_changed_files: false))
    end

    def push_data
      @push_data ||= Gitlab::DataBuilder::Push.build(push_data_params(commits: limited_commits))

      # Dependent code may modify the push data, so return a duplicate each time
      @push_data.dup
    end

    # to be overridden in EE
    def pipeline_options
      {}
    end

    def update_remote_mirrors
      return unless project.has_remote_mirror?

      project.mark_stuck_remote_mirrors_as_failed!
      project.update_remote_mirrors
    end

    def log_pipeline_errors(exception)
      data = {
        class: self.class.name,
        correlation_id: Labkit::Correlation::CorrelationId.current_id.to_s,
        project_id: project.id,
        project_path: project.full_path,
        message: "Error creating pipeline",
        errors: exception.to_s,
        pipeline_params: pipeline_params
      }

      logger.warn(data)
    end

    def logger
      if Sidekiq.server?
        Sidekiq.logger
      else
        # This service runs in Sidekiq, so this shouldn't ever be
        # called, but this is included just in case.
        Gitlab::ProjectServiceLogger
      end
    end
  end
end