pipeline.rb 41.7 KB
Newer Older
1 2
# frozen_string_literal: true

3
module Ci
4
  class Pipeline < ApplicationRecord
5
    extend Gitlab::Ci::Model
6
    include Ci::HasStatus
7
    include Importable
8
    include AfterCommitQueue
9
    include Presentable
10
    include Gitlab::Allowable
11
    include Gitlab::OptimisticLocking
12
    include Gitlab::Utils::StrongMemoize
13
    include AtomicInternalId
14
    include EnumWithNil
15
    include Ci::HasRef
16
    include ShaAttribute
17
    include FromUnion
18
    include UpdatedAtFilterable
19

20 21
    MAX_OPEN_MERGE_REQUESTS_REFS = 4

Shinya Maeda's avatar
Shinya Maeda committed
22 23 24
    PROJECT_ROUTE_AND_NAMESPACE_ROUTE = {
      project: [:project_feature, :route, { namespace: :route }]
    }.freeze
25 26
    CONFIG_EXTENSION = '.gitlab-ci.yml'
    DEFAULT_CONFIG_PATH = CONFIG_EXTENSION
Shinya Maeda's avatar
Shinya Maeda committed
27

28 29
    BridgeStatusError = Class.new(StandardError)

30 31
    sha_attribute :source_sha
    sha_attribute :target_sha
Kamil Trzcinski's avatar
WIP  
Kamil Trzcinski committed
32

33 34 35 36 37 38 39
    # Ci::CreatePipelineService returns Ci::Pipeline so this is the only place
    # where we can pass additional information from the service. This accessor
    # is used for storing the processed CI YAML contents for linting purposes.
    # There is an open issue to address this:
    # https://gitlab.com/gitlab-org/gitlab/-/issues/259010
    attr_accessor :merged_yaml

40
    belongs_to :project, inverse_of: :all_pipelines
41
    belongs_to :user
42
    belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
43
    belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
Shinya Maeda's avatar
Shinya Maeda committed
44
    belongs_to :merge_request, class_name: 'MergeRequest'
45
    belongs_to :external_pull_request
46
    belongs_to :ci_ref, class_name: 'Ci::Ref', foreign_key: :ci_ref_id, inverse_of: :pipelines
47

48 49 50 51 52 53 54 55 56 57
    has_internal_id :iid, scope: :project, presence: false,
      track_if: -> { !importing? },
      ensure_if: -> { !importing? },
      init: ->(pipeline, scope) do
        if pipeline
          pipeline.project&.all_pipelines&.maximum(:iid) || pipeline.project&.all_pipelines&.count
        elsif scope
          ::Ci::Pipeline.where(**scope).maximum(:iid)
        end
      end
58

59
    has_many :stages, -> { order(position: :asc) }, inverse_of: :pipeline
60
    has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
61
    has_many :latest_statuses_ordered_by_stage, -> { latest.order(:stage_idx, :stage) }, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
62
    has_many :latest_statuses, -> { latest }, class_name: 'CommitStatus', foreign_key: :commit_id, inverse_of: :pipeline
63
    has_many :processables, class_name: 'Ci::Processable', foreign_key: :commit_id, inverse_of: :pipeline
64
    has_many :bridges, class_name: 'Ci::Bridge', foreign_key: :commit_id, inverse_of: :pipeline
65
    has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline
66
    has_many :job_artifacts, through: :builds
67
    has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
68
    has_many :variables, class_name: 'Ci::PipelineVariable'
69 70
    has_many :deployments, through: :builds
    has_many :environments, -> { distinct }, through: :deployments
Matt Kasa's avatar
Matt Kasa committed
71 72
    has_many :latest_builds, -> { latest.with_project_and_metadata }, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'Ci::Build'
    has_many :downloadable_artifacts, -> { not_expired.downloadable.with_job }, through: :latest_builds, source: :job_artifacts
Felipe Artur's avatar
Felipe Artur committed
73

74 75
    has_many :messages, class_name: 'Ci::PipelineMessage', inverse_of: :pipeline

Felipe Artur's avatar
Felipe Artur committed
76 77
    # Merge requests for which the current pipeline is running against
    # the merge request's latest commit.
78
    has_many :merge_requests_as_head_pipeline, foreign_key: "head_pipeline_id", class_name: 'MergeRequest'
79

80
    has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
81
    has_many :failed_builds, -> { latest.failed }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
82
    has_many :retryable_builds, -> { latest.failed_or_canceled.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
83
    has_many :cancelable_statuses, -> { cancelable }, foreign_key: :commit_id, class_name: 'CommitStatus'
84 85
    has_many :manual_actions, -> { latest.manual_actions.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
    has_many :scheduled_actions, -> { latest.scheduled_actions.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
86

87 88
    has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
    has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
89
    has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_pipeline_id
90

91
    has_one :source_pipeline, class_name: 'Ci::Sources::Pipeline', inverse_of: :pipeline
92

93 94
    has_one :chat_data, class_name: 'Ci::PipelineChatData'

95
    has_many :triggered_pipelines, through: :sourced_pipelines, source: :pipeline
96
    has_many :child_pipelines, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :sourced_pipelines, source: :pipeline
97
    has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline
98
    has_one :parent_pipeline, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :source_pipeline, source: :source_pipeline
99
    has_one :source_job, through: :source_pipeline, source: :source_job
100
    has_one :source_bridge, through: :source_pipeline, source: :source_bridge
101

102 103
    has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline

104
    has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult', foreign_key: :last_pipeline_id
105
    has_many :latest_builds_report_results, through: :latest_builds, source: :report_results
106
    has_many :pipeline_artifacts, class_name: 'Ci::PipelineArtifact', inverse_of: :pipeline, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
107

108 109
    accepts_nested_attributes_for :variables, reject_if: :persisted?

110
    delegate :full_path, to: :project, prefix: true
Douwe Maan's avatar
Douwe Maan committed
111

Douwe Maan's avatar
Douwe Maan committed
112 113
    validates :sha, presence: { unless: :importing? }
    validates :ref, presence: { unless: :importing? }
114
    validates :tag, inclusion: { in: [false], if: :merge_request? }
115 116 117 118 119

    validates :external_pull_request, presence: { if: :external_pull_request_event? }
    validates :external_pull_request, absence: { unless: :external_pull_request_event? }
    validates :tag, inclusion: { in: [false], if: :external_pull_request_event? }

Douwe Maan's avatar
Douwe Maan committed
120
    validates :status, presence: { unless: :importing? }
121
    validate :valid_commit_sha, unless: :importing?
Jasper Maes's avatar
Jasper Maes committed
122
    validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
123

124
    after_create :keep_around_commits, unless: :importing?
Kamil Trzcinski's avatar
Kamil Trzcinski committed
125

126
    # We use `Enums::Ci::Pipeline.sources` here so that EE can more easily extend
127
    # this `Hash` with new values.
128
    enum_with_nil source: Enums::Ci::Pipeline.sources
129

130
    enum_with_nil config_source: Enums::Ci::Pipeline.config_sources
131

132
    # We use `Enums::Ci::Pipeline.failure_reasons` here so that EE can more easily
133
    # extend this `Hash` with new values.
134
    enum failure_reason: Enums::Ci::Pipeline.failure_reasons
135

136 137
    enum locked: { unlocked: 0, artifacts_locked: 1 }

138
    state_machine :status, initial: :created do
139
      event :enqueue do
140
        transition [:created, :manual, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending
141
        transition [:success, :failed, :canceled] => :running
142 143 144

        # this is needed to ensure tests to be covered
        transition [:running] => :running
145 146
      end

147 148 149 150
      event :request_resource do
        transition any - [:waiting_for_resource] => :waiting_for_resource
      end

Tiger's avatar
Tiger committed
151 152 153 154
      event :prepare do
        transition any - [:preparing] => :preparing
      end

155
      event :run do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
156
        transition any - [:running] => :running
157 158
      end

159
      event :skip do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
160
        transition any - [:skipped] => :skipped
161 162 163
      end

      event :drop do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
164
        transition any - [:failed] => :failed
165 166
      end

167
      event :succeed do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
168
        transition any - [:success] => :success
169 170 171
      end

      event :cancel do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
172
        transition any - [:canceled] => :canceled
173 174
      end

175
      event :block do
176
        transition any - [:manual] => :manual
177 178
      end

Shinya Maeda's avatar
Shinya Maeda committed
179 180 181 182
      event :delay do
        transition any - [:scheduled] => :scheduled
      end

183 184 185 186
      # IMPORTANT
      # Do not add any operations to this state_machine
      # Create a separate worker for each new operation

187
      before_transition [:created, :waiting_for_resource, :preparing, :pending] => :running do |pipeline|
188
        pipeline.started_at = Time.current
189 190
      end

191
      before_transition any => [:success, :failed, :canceled] do |pipeline|
192
        pipeline.finished_at = Time.current
193 194 195
        pipeline.update_duration
      end

196 197 198 199
      before_transition any => [:manual] do |pipeline|
        pipeline.update_duration
      end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
200
      before_transition canceled: any - [:canceled] do |pipeline|
Lin Jen-Shin's avatar
Lin Jen-Shin committed
201 202 203
        pipeline.auto_canceled_by = nil
      end

204 205 206 207 208 209
      before_transition any => :failed do |pipeline, transition|
        transition.args.first.try do |reason|
          pipeline.failure_reason = reason
        end
      end

210
      after_transition [:created, :waiting_for_resource, :preparing, :pending] => :running do |pipeline|
211
        pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
212 213 214
      end

      after_transition any => [:success] do |pipeline|
215
        pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
216 217
      end

218
      after_transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success do |pipeline|
219 220 221
        # We wait a little bit to ensure that all BuildFinishedWorkers finish first
        # because this is where some metrics like code coverage is parsed and stored
        # in CI build records which the daily build metrics worker relies on.
222
        pipeline.run_after_commit { Ci::DailyBuildGroupReportResultsWorker.perform_in(10.minutes, pipeline.id) }
223
      end
224 225

      after_transition do |pipeline, transition|
226 227 228
        next if transition.loopback?

        pipeline.run_after_commit do
229
          PipelineHooksWorker.perform_async(pipeline.id)
230
          ExpirePipelineCacheWorker.perform_async(pipeline.id) if pipeline.cacheable?
231
        end
232
      end
233

234 235
      after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
        pipeline.run_after_commit do
236 237
          pipeline.persistent_ref.delete

238 239 240 241 242
          pipeline.all_merge_requests.each do |merge_request|
            next unless merge_request.auto_merge_enabled?

            AutoMergeProcessWorker.perform_async(merge_request.id)
          end
243 244 245 246

          if pipeline.auto_devops_source?
            self.class.auto_devops_pipelines_completed_total.increment(status: pipeline.status)
          end
247 248 249
        end
      end

250 251
      after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
        pipeline.run_after_commit do
252
          ::Ci::PipelineArtifacts::CoverageReportWorker.perform_async(pipeline.id)
253
          ::Ci::PipelineArtifacts::CreateQualityReportWorker.perform_async(pipeline.id)
254 255 256
        end
      end

257 258 259 260 261 262 263 264
      after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
        next unless pipeline.bridge_waiting?

        pipeline.run_after_commit do
          ::Ci::PipelineBridgeStatusWorker.perform_async(pipeline.id)
        end
      end

265 266 267 268 269 270 271 272
      after_transition any => any do |pipeline|
        pipeline.run_after_commit do
          # Passing the seq-id ensures this is idempotent
          seq_id = ::Atlassian::JiraConnect::Client.generate_update_sequence_id
          ::JiraConnect::SyncBuildsWorker.perform_async(pipeline.id, seq_id)
        end
      end

273 274 275 276 277 278
      after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
        pipeline.run_after_commit do
          ::Ci::TestFailureHistoryService.new(pipeline).async.perform_if_needed # rubocop: disable CodeReuse/ServiceClass
        end
      end

279
      after_transition any => [:success, :failed] do |pipeline|
280 281
        ref_status = pipeline.ci_ref&.update_status_by!(pipeline)

282
        pipeline.run_after_commit do
283
          PipelineNotificationWorker.perform_async(pipeline.id, ref_status: ref_status)
284
        end
285
      end
286 287 288 289 290 291

      after_transition any => [:failed] do |pipeline|
        next unless pipeline.auto_devops_source?

        pipeline.run_after_commit { AutoDevops::DisableWorker.perform_async(pipeline.id) }
      end
292 293
    end

294
    scope :internal, -> { where(source: internal_sources) }
295
    scope :no_child, -> { where.not(source: :parent_pipeline) }
296
    scope :ci_sources, -> { where(source: Enums::Ci::Pipeline.ci_sources.values) }
297
    scope :ci_branch_sources, -> { where(source: Enums::Ci::Pipeline.ci_branch_sources.values) }
298
    scope :ci_and_parent_sources, -> { where(source: Enums::Ci::Pipeline.ci_and_parent_sources.values) }
299
    scope :for_user, -> (user) { where(user: user) }
300 301 302
    scope :for_sha, -> (sha) { where(sha: sha) }
    scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) }
    scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) }
303
    scope :for_ref, -> (ref) { where(ref: ref) }
304
    scope :for_branch, -> (branch) { for_ref(branch).where(tag: false) }
305
    scope :for_id, -> (id) { where(id: id) }
306
    scope :for_iid, -> (iid) { where(iid: iid) }
307
    scope :for_project, -> (project_id) { where(project_id: project_id) }
308
    scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
309 310 311 312 313 314
    scope :created_before_id, -> (id) { where('ci_pipelines.id < ?', id) }
    scope :before_pipeline, -> (pipeline) { created_before_id(pipeline.id).outside_pipeline_family(pipeline) }

    scope :outside_pipeline_family, ->(pipeline) do
      where.not(id: pipeline.same_family_pipeline_ids)
    end
315

316 317 318 319
    scope :with_reports, -> (reports_scope) do
      where('EXISTS (?)', ::Ci::Build.latest.with_reports(reports_scope).where('ci_pipelines.id=ci_builds.commit_id').select(1))
    end

320
    scope :with_only_interruptible_builds, -> do
321 322 323 324 325 326 327
      where('NOT EXISTS (?)',
        Ci::Build.where('ci_builds.commit_id = ci_pipelines.id')
                 .with_status(:running, :success, :failed)
                 .not_interruptible
      )
    end

328 329 330 331
    # Returns the pipelines that associated with the given merge request.
    # In general, please use `Ci::PipelinesForMergeRequestFinder` instead,
    # for checking permission of the actor.
    scope :triggered_by_merge_request, -> (merge_request) do
332 333 334
      where(source: :merge_request_event,
            merge_request: merge_request,
            project: [merge_request.source_project, merge_request.target_project])
335 336
    end

337 338 339 340 341
    # Returns the pipelines in descending order (= newest first), optionally
    # limited to a number of references.
    #
    # ref - The name (or names) of the branch(es)/tag(s) to limit the list of
    #       pipelines to.
342
    # sha - The commit SHA (or mutliple SHAs) to limit the list of pipelines to.
343
    # limit - This limits a backlog search, default to 100.
344
    def self.newest_first(ref: nil, sha: nil, limit: 100)
345
      relation = order(id: :desc)
346
      relation = relation.where(ref: ref) if ref
347
      relation = relation.where(sha: sha) if sha
348

Kamil Trzciński's avatar
Kamil Trzciński committed
349 350 351 352
      if limit
        ids = relation.limit(limit).select(:id)
        relation = relation.where(id: ids)
      end
Kamil Trzciński's avatar
Kamil Trzciński committed
353

354
      relation
355
    end
356

357
    def self.latest_status(ref = nil)
358
      newest_first(ref: ref).pluck(:status).first
359 360
    end

361
    def self.latest_successful_for_ref(ref)
362
      newest_first(ref: ref).success.take
363 364
    end

365 366 367 368
    def self.latest_successful_for_sha(sha)
      newest_first(sha: sha).success.take
    end

369
    def self.latest_successful_for_refs(refs)
370
      relation = newest_first(ref: refs).success
371 372

      relation.each_with_object({}) do |pipeline, hash|
373 374 375 376
        hash[pipeline.ref] ||= pipeline
      end
    end

377 378 379 380 381 382 383 384
    def self.latest_running_for_ref(ref)
      newest_first(ref: ref).running.take
    end

    def self.latest_failed_for_ref(ref)
      newest_first(ref: ref).failed.take
    end

385
    # Returns a Hash containing the latest pipeline for every given
386 387
    # commit.
    #
388
    # The keys of this Hash are the commit SHAs, the values the pipelines.
389
    #
390
    # commits - The list of commit SHAs to get the pipelines for.
391
    # ref - The ref to scope the data to (e.g. "master"). If the ref is not
392 393
    #       given we simply get the latest pipelines for the commits, regardless
    #       of what refs the pipelines belong to.
Kamil Trzciński's avatar
Kamil Trzciński committed
394
    def self.latest_pipeline_per_commit(commits, ref = nil)
395 396 397 398 399 400 401 402 403 404 405 406 407
      p1 = arel_table
      p2 = arel_table.alias

      # This LEFT JOIN will filter out all but the newest row for every
      # combination of (project_id, sha) or (project_id, sha, ref) if a ref is
      # given.
      cond = p1[:sha].eq(p2[:sha])
        .and(p1[:project_id].eq(p2[:project_id]))
        .and(p1[:id].lt(p2[:id]))

      cond = cond.and(p1[:ref].eq(p2[:ref])) if ref
      join = p1.join(p2, Arel::Nodes::OuterJoin).on(cond)

408
      relation = where(sha: commits)
409 410 411 412 413
        .where(p2[:id].eq(nil))
        .joins(join.join_sources)

      relation = relation.where(ref: ref) if ref

414
      relation.each_with_object({}) do |pipeline, hash|
Kamil Trzciński's avatar
Kamil Trzciński committed
415
        hash[pipeline.sha] = pipeline
416 417 418
      end
    end

419 420 421 422
    def self.latest_successful_ids_per_project
      success.group(:project_id).select('max(id) as id')
    end

423 424 425 426
    def self.last_finished_for_ref_id(ci_ref_id)
      where(ci_ref_id: ci_ref_id).ci_sources.finished.order(id: :desc).select(:id).take
    end

427 428 429 430
    def self.truncate_sha(sha)
      sha[0...8]
    end

431
    def self.total_duration
Lin Jen-Shin's avatar
Lin Jen-Shin committed
432
      where.not(duration: nil).sum(:duration)
433 434
    end

435 436 437 438
    def self.internal_sources
      sources.reject { |source| source == "external" }.values
    end

439
    def self.bridgeable_statuses
440
      ::Ci::Pipeline::AVAILABLE_STATUSES - %w[created waiting_for_resource preparing pending]
441 442
    end

443 444 445 446
    def self.auto_devops_pipelines_completed_total
      @auto_devops_pipelines_completed_total ||= Gitlab::Metrics.counter(:auto_devops_pipelines_completed_total, 'Number of completed auto devops pipelines')
    end

447 448
    def stages_count
      statuses.select(:stage).distinct.count
Kamil Trzcinski's avatar
Kamil Trzcinski committed
449 450
    end

451 452 453 454
    def total_size
      statuses.count(:id)
    end

455
    def stages_names
456 457
      statuses.order(:stage_idx).distinct
        .pluck(:stage, :stage_idx).map(&:first)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
458 459
    end

460
    def legacy_stage(name)
461
      stage = Ci::LegacyStage.new(self, name: name)
462
      stage unless stage.statuses_count == 0
463 464
    end

465
    def ref_exists?
466
      project.repository.ref_exists?(git_ref)
467 468
    rescue Gitlab::Git::Repository::NoRepository
      false
469 470
    end

Kamil Trzciński's avatar
Kamil Trzciński committed
471
    def legacy_stages_using_composite_status
472
      stages = latest_statuses_ordered_by_stage.group_by(&:stage)
Kamil Trzciński's avatar
Kamil Trzciński committed
473

Kamil Trzciński's avatar
Kamil Trzciński committed
474
      stages.map do |stage_name, jobs|
Kamil Trzciński's avatar
Kamil Trzciński committed
475
        composite_status = Gitlab::Ci::Status::Composite
Kamil Trzciński's avatar
Kamil Trzciński committed
476
          .new(jobs)
Kamil Trzciński's avatar
Kamil Trzciński committed
477 478

        Ci::LegacyStage.new(self,
Kamil Trzciński's avatar
Kamil Trzciński committed
479 480 481
          name: stage_name,
          status: composite_status.status,
          warnings: composite_status.warnings?)
Kamil Trzciński's avatar
Kamil Trzciński committed
482
      end
Kamil Trzciński's avatar
Kamil Trzciński committed
483 484
    end

485 486 487 488
    def triggered_pipelines_with_preloads
      triggered_pipelines.preload(:source_job)
    end

489
    # TODO: Remove usage of this method in templates
Kamil Trzciński's avatar
Kamil Trzciński committed
490
    def legacy_stages
491
      legacy_stages_using_composite_status
Kamil Trzcinski's avatar
Kamil Trzcinski committed
492 493
    end

494
    def valid_commit_sha
495
      if self.sha == Gitlab::Git::BLANK_SHA
496 497 498 499 500
        self.errors.add(:sha, " cant be 00000000 (branch removal)")
      end
    end

    def git_author_name
501 502 503
      strong_memoize(:git_author_name) do
        commit.try(:author_name)
      end
504 505 506
    end

    def git_author_email
507 508 509
      strong_memoize(:git_author_email) do
        commit.try(:author_email)
      end
510 511 512
    end

    def git_commit_message
513 514 515
      strong_memoize(:git_commit_message) do
        commit.try(:message)
      end
516 517
    end

518
    def git_commit_title
519 520 521
      strong_memoize(:git_commit_title) do
        commit.try(:title)
      end
522 523
    end

524
    def git_commit_full_title
525 526 527
      strong_memoize(:git_commit_full_title) do
        commit.try(:full_title)
      end
528 529 530
    end

    def git_commit_description
531 532 533
      strong_memoize(:git_commit_description) do
        commit.try(:description)
      end
534 535
    end

536 537 538 539 540 541
    def git_commit_timestamp
      strong_memoize(:git_commit_timestamp) do
        commit.try(:timestamp)
      end
    end

542 543 544 545
    def before_sha
      super || Gitlab::Git::BLANK_SHA
    end

546
    def short_sha
547
      Ci::Pipeline.truncate_sha(sha)
548 549
    end

550 551 552 553
    # NOTE: This is loaded lazily and will never be nil, even if the commit
    # cannot be found.
    #
    # Use constructs like: `pipeline.commit.present?`
554
    def commit
555
      @commit ||= Commit.lazy(project, sha)
556 557
    end

558
    def stuck?
559
      pending_builds.any?(&:stuck?)
560 561
    end

562
    def retryable?
563
      retryable_builds.any?
564 565
    end

566
    def cancelable?
567
      cancelable_statuses.any?
568 569
    end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
570 571 572 573
    def auto_canceled?
      canceled? && auto_canceled_by_id?
    end

574
    def cancel_running(retries: nil)
575
      retry_optimistic_lock(cancelable_statuses, retries) do |cancelable|
Lin Jen-Shin's avatar
Lin Jen-Shin committed
576 577 578
        cancelable.find_each do |job|
          yield(job) if block_given?
          job.cancel
579
        end
Lin Jen-Shin's avatar
Lin Jen-Shin committed
580
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
581 582
    end

583
    def auto_cancel_running(pipeline, retries: nil)
584 585
      update(auto_canceled_by: pipeline)

586
      cancel_running(retries: retries) do |job|
587
        job.auto_canceled_by = pipeline
588
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
589 590
    end

591
    # rubocop: disable CodeReuse/ServiceClass
592
    def retry_failed(current_user)
593 594
      Ci::RetryPipelineService.new(project, current_user)
        .execute(self)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
595
    end
596
    # rubocop: enable CodeReuse/ServiceClass
Kamil Trzcinski's avatar
Kamil Trzcinski committed
597

598 599 600 601 602 603 604
    def lazy_ref_commit
      return unless ::Gitlab::Ci::Features.pipeline_latest?

      BatchLoader.for(ref).batch do |refs, loader|
        next unless project.repository_exists?

        project.repository.list_commits_by_ref_name(refs).then do |commits|
605
          commits.each { |key, commit| loader.call(key, commits[key]) }
606 607 608 609
        end
      end
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
610
    def latest?
611
      return false unless git_ref && commit.present?
612

613 614 615 616 617 618 619
      unless ::Gitlab::Ci::Features.pipeline_latest?
        return project.commit(git_ref) == commit
      end

      return false if lazy_ref_commit.nil?

      lazy_ref_commit.id == commit.id
Kamil Trzcinski's avatar
Kamil Trzcinski committed
620 621
    end

622
    def retried
623
      @retried ||= (statuses.order(id: :desc) - latest_statuses)
624 625 626
    end

    def coverage
627
      coverage_array = latest_statuses.map(&:coverage).compact
628 629
      if coverage_array.size >= 1
        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
630 631 632
      end
    end

633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
    def batch_lookup_report_artifact_for_file_type(file_type)
      latest_report_artifacts
        .values_at(*::Ci::JobArtifact.associated_file_types_for(file_type.to_s))
        .flatten
        .compact
        .last
    end

    # This batch loads the latest reports for each CI job artifact
    # type (e.g. sast, dast, etc.) in a single SQL query to eliminate
    # the need to do N different `job_artifacts.where(file_type:
    # X).last` calls.
    #
    # Return a hash of file type => array of 1 job artifact
    def latest_report_artifacts
      ::Gitlab::SafeRequestStore.fetch("pipeline:#{self.id}:latest_report_artifacts") do
        # Note we use read_attribute(:project_id) to read the project
        # ID instead of self.project_id. The latter appears to load
        # the Project model. This extra filter doesn't appear to
        # affect query plan but included to ensure we don't leak the
        # wrong informaiton.
        ::Ci::JobArtifact.where(
          id: job_artifacts.with_reports
            .select('max(ci_job_artifacts.id) as id')
            .where(project_id: self.read_attribute(:project_id))
            .group(:file_type)
        )
          .preload(:job)
          .group_by(&:file_type)
      end
    end

665
    def has_kubernetes_active?
666
      project.deployment_platform&.active?
667 668
    end

669 670 671 672
    def freeze_period?
      Ci::FreezePeriodStatus.new(project: project).execute
    end

Connor Shea's avatar
Connor Shea committed
673
    def has_warnings?
674
      number_of_warnings > 0
675 676 677 678
    end

    def number_of_warnings
      BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader|
Cong Chen's avatar
Cong Chen committed
679
        ::CommitStatus.where(commit_id: pipeline_ids)
680 681 682 683 684 685
          .latest
          .failed_but_allowed
          .group(:commit_id)
          .count
          .each { |id, amount| loader.call(id, amount) }
      end
686 687
    end

688 689 690 691 692 693 694
    def needs_processing?
      statuses
        .where(processed: [false, nil])
        .latest
        .exists?
    end

695 696 697
    # TODO: this logic is duplicate with Pipeline::Chain::Config::Content
    # we should persist this is `ci_pipelines.config_path`
    def config_path
698 699
      return unless repository_source? || unknown_source?

700
      project.ci_config_path_or_default
701 702
    end

703 704 705 706
    def has_yaml_errors?
      yaml_errors.present?
    end

707
    def add_error_message(content)
708 709 710 711 712 713 714 715 716 717 718 719 720
      add_message(:error, content)
    end

    def add_warning_message(content)
      add_message(:warning, content)
    end

    # We can't use `messages.error` scope here because messages should also be
    # read when the pipeline is not persisted. Using the scope will return no
    # results as it would query persisted data.
    def error_messages
      messages.select(&:error?)
    end
721

722 723 724 725
    def warning_messages(limit: nil)
      messages.select(&:warning?).tap do |warnings|
        break warnings.take(limit) if limit
      end
726 727
    end

James Lopez's avatar
James Lopez committed
728 729 730
    # Manually set the notes for a Ci::Pipeline
    # There is no ActiveRecord relation between Ci::Pipeline and notes
    # as they are related to a commit sha. This method helps importing
731
    # them using the +Gitlab::ImportExport::Project::RelationFactory+ class.
James Lopez's avatar
James Lopez committed
732 733 734 735 736 737 738 739 740
    def notes=(notes)
      notes.each do |note|
        note[:id] = nil
        note[:commit_id] = sha
        note[:noteable_id] = self['id']
        note.save!
      end
    end

741
    def notes
742
      project.notes.for_commit_id(sha)
743 744
    end

745
    def set_status(new_status)
746
      retry_optimistic_lock(self) do
Kamil Trzciński's avatar
Kamil Trzciński committed
747
        case new_status
748
        when 'created' then nil
749
        when 'waiting_for_resource' then request_resource
Tiger's avatar
Tiger committed
750
        when 'preparing' then prepare
751 752 753 754 755 756
        when 'pending' then enqueue
        when 'running' then run
        when 'success' then succeed
        when 'failed' then drop
        when 'canceled' then cancel
        when 'skipped' then skip
757
        when 'manual' then block
Shinya Maeda's avatar
Shinya Maeda committed
758
        when 'scheduled' then delay
759
        else
760
          raise Ci::HasStatus::UnknownStatusError,
Kamil Trzciński's avatar
Kamil Trzciński committed
761
                "Unknown status `#{new_status}`"
762
        end
763
      end
764 765
    end

766
    def protected_ref?
767
      strong_memoize(:protected_ref) { project.protected_for?(git_ref) }
768 769 770
    end

    def legacy_trigger
771
      strong_memoize(:legacy_trigger) { trigger_requests.first }
772 773
    end

774 775
    def persisted_variables
      Gitlab::Ci::Variables::Collection.new.tap do |variables|
776 777 778 779
        break variables unless persisted?

        variables.append(key: 'CI_PIPELINE_ID', value: id.to_s)
        variables.append(key: 'CI_PIPELINE_URL', value: Gitlab::Routing.url_helpers.project_pipeline_url(project, self))
780 781 782
      end
    end

783
    def predefined_variables
784 785 786
      Gitlab::Ci::Variables::Collection.new.tap do |variables|
        variables.append(key: 'CI_PIPELINE_IID', value: iid.to_s)
        variables.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
787 788 789 790

        variables.append(key: 'CI_CONFIG_PATH', value: config_path)

        variables.concat(predefined_commit_variables)
791

792
        if merge_request?
793
          variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s)
794 795
          variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s)
          variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s)
796

797 798 799 800
          diff = self.merge_request_diff
          if diff.present?
            variables.append(key: 'CI_MERGE_REQUEST_DIFF_ID', value: diff.id.to_s)
            variables.append(key: 'CI_MERGE_REQUEST_DIFF_BASE_SHA', value: diff.base_commit_sha)
801 802
          end

803 804
          variables.concat(merge_request.predefined_variables)
        end
805

806
        if open_merge_requests_refs.any?
807 808 809
          variables.append(key: 'CI_OPEN_MERGE_REQUESTS', value: open_merge_requests_refs.join(','))
        end

810
        variables.append(key: 'CI_KUBERNETES_ACTIVE', value: 'true') if has_kubernetes_active?
811
        variables.append(key: 'CI_DEPLOY_FREEZE', value: 'true') if freeze_period?
812

813 814 815
        if external_pull_request_event? && external_pull_request
          variables.concat(external_pull_request.predefined_variables)
        end
816
      end
817 818
    end

819 820 821 822 823 824 825
    def predefined_commit_variables
      Gitlab::Ci::Variables::Collection.new.tap do |variables|
        variables.append(key: 'CI_COMMIT_SHA', value: sha)
        variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha)
        variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
        variables.append(key: 'CI_COMMIT_REF_NAME', value: source_ref)
        variables.append(key: 'CI_COMMIT_REF_SLUG', value: source_ref_slug)
826
        variables.append(key: 'CI_COMMIT_BRANCH', value: ref) if branch?
827 828 829 830 831
        variables.append(key: 'CI_COMMIT_TAG', value: ref) if tag?
        variables.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
        variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
        variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
        variables.append(key: 'CI_COMMIT_REF_PROTECTED', value: (!!protected_ref?).to_s)
832
        variables.append(key: 'CI_COMMIT_TIMESTAMP', value: git_commit_timestamp.to_s)
833 834 835 836 837 838 839 840 841 842

        # legacy variables
        variables.append(key: 'CI_BUILD_REF', value: sha)
        variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
        variables.append(key: 'CI_BUILD_REF_NAME', value: source_ref)
        variables.append(key: 'CI_BUILD_REF_SLUG', value: source_ref_slug)
        variables.append(key: 'CI_BUILD_TAG', value: ref) if tag?
      end
    end

843 844 845 846
    def queued_duration
      return unless started_at

      seconds = (started_at - created_at).to_i
847
      seconds unless seconds == 0
848 849
    end

850
    def update_duration
851 852
      return unless started_at

853
      self.duration = Gitlab::Ci::Pipeline::Duration.from_pipeline(self)
854 855 856
    end

    def execute_hooks
857 858
      project.execute_hooks(pipeline_data, :pipeline_hooks) if project.has_active_hooks?(:pipeline_hooks)
      project.execute_services(pipeline_data, :pipeline_hooks) if project.has_active_services?(:pipeline_hooks)
859 860
    end

861 862
    # All the merge requests for which the current pipeline runs/ran against
    def all_merge_requests
Shinya Maeda's avatar
Shinya Maeda committed
863
      @all_merge_requests ||=
864
        if merge_request?
865
          MergeRequest.where(id: merge_request_id)
Shinya Maeda's avatar
Shinya Maeda committed
866
        else
867
          MergeRequest.where(source_project_id: project_id, source_branch: ref)
868
            .by_commit_sha(sha)
Shinya Maeda's avatar
Shinya Maeda committed
869
        end
870 871
    end

872 873 874 875
    def all_merge_requests_by_recency
      all_merge_requests.order(id: :desc)
    end

876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
    # This returns a list of MRs that point
    # to the same source project/branch
    def related_merge_requests
      if merge_request?
        # We look for all other MRs that this branch might be pointing to
        MergeRequest.where(
          source_project_id: merge_request.source_project_id,
          source_branch: merge_request.source_branch)
      else
        MergeRequest.where(
          source_project_id: project_id,
          source_branch: ref)
      end
    end

    # We cannot use `all_merge_requests`, due to race condition
    # This returns a list of at most 4 open MRs
    def open_merge_requests_refs
      strong_memoize(:open_merge_requests_refs) do
        # We ensure that triggering user can actually read the pipeline
        related_merge_requests
          .opened
          .limit(MAX_OPEN_MERGE_REQUESTS_REFS)
          .order(id: :desc)
          .preload(:target_project)
          .select { |mr| can?(user, :read_merge_request, mr) }
          .map { |mr| mr.to_reference(project, full: true) }
      end
    end

906
    def same_family_pipeline_ids
907 908 909
      ::Gitlab::Ci::PipelineObjectHierarchy.new(
        self.class.where(id: root_ancestor), options: { same_project: true }
      ).base_and_descendants.select(:id)
910 911
    end

912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930
    def build_with_artifacts_in_self_and_descendants(name)
      builds_in_self_and_descendants
        .ordered_by_pipeline # find job in hierarchical order
        .with_downloadable_artifacts
        .find_by_name(name)
    end

    def builds_in_self_and_descendants
      Ci::Build.latest.where(pipeline: self_and_descendants)
    end

    # Without using `unscoped`, caller scope is also included into the query.
    # Using `unscoped` here will be redundant after Rails 6.1
    def self_and_descendants
      ::Gitlab::Ci::PipelineObjectHierarchy
        .new(self.class.unscoped.where(id: id), options: { same_project: true })
        .base_and_descendants
    end

931 932 933 934 935 936 937 938 939
    def root_ancestor
      return self unless child?

      Gitlab::Ci::PipelineObjectHierarchy
        .new(self.class.unscoped.where(id: id), options: { same_project: true })
        .base_and_ancestors(hierarchy_order: :desc)
        .first
    end

940 941 942 943 944 945 946 947
    def bridge_triggered?
      source_bridge.present?
    end

    def bridge_waiting?
      source_bridge&.dependent?
    end

948
    def child?
949 950
      parent_pipeline? && # child pipelines have `parent_pipeline` source
        parent_pipeline.present?
951 952 953 954 955 956
    end

    def parent?
      child_pipelines.exists?
    end

957 958 959 960
    def created_successfully?
      persisted? && failure_reason.blank?
    end

961
    def detailed_status(current_user)
962
      Gitlab::Ci::Status::Pipeline::Factory
963
        .new(self.present, current_user)
964
        .fabricate!
965 966
    end

967
    def find_job_with_archive_artifacts(name)
968
      builds.latest.with_downloadable_artifacts.find_by_name(name)
969 970
    end

971
    def latest_builds_with_artifacts
972 973 974
      # We purposely cast the builds to an Array here. Because we always use the
      # rows if there are more than 0 this prevents us from having to run two
      # queries: one to get the count and one to get the rows.
975
      @latest_builds_with_artifacts ||= builds.latest.with_artifacts_not_expired.to_a
976 977
    end

978 979 980 981
    def latest_report_builds(reports_scope = ::Ci::JobArtifact.with_reports)
      builds.latest.with_reports(reports_scope)
    end

982 983 984 985
    def latest_test_report_builds
      latest_report_builds(Ci::JobArtifact.test_reports).preload(:project)
    end

986
    def builds_with_coverage
987
      builds.latest.with_coverage
988 989
    end

990 991 992 993
    def builds_with_failed_tests(limit: nil)
      latest_test_report_builds.failed.limit(limit)
    end

994
    def has_reports?(reports_scope)
995
      complete? && latest_report_builds(reports_scope).exists?
996 997
    end

998
    def has_coverage_reports?
999
      pipeline_artifacts&.report_exists?(:code_coverage)
1000 1001 1002 1003
    end

    def can_generate_coverage_reports?
      has_reports?(Ci::JobArtifact.coverage_reports)
1004 1005
    end

1006
    def has_codequality_mr_diff_report?
1007
      pipeline_artifacts&.report_exists?(:code_quality_mr_diff)
1008 1009 1010
    end

    def can_generate_codequality_reports?
1011
      return false unless ::Gitlab::Ci::Features.display_quality_on_mr_diff?(project)
1012

1013 1014 1015
      has_reports?(Ci::JobArtifact.codequality_reports)
    end

1016
    def test_report_summary
1017 1018 1019
      strong_memoize(:test_report_summary) do
        Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)
      end
1020 1021
    end

1022 1023
    def test_reports
      Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
1024
        latest_test_report_builds.find_each do |build|
1025 1026 1027 1028 1029
          build.collect_test_reports!(test_reports)
        end
      end
    end

1030 1031
    def accessibility_reports
      Gitlab::Ci::Reports::AccessibilityReports.new.tap do |accessibility_reports|
1032
        latest_report_builds(Ci::JobArtifact.accessibility_reports).each do |build|
1033 1034 1035 1036 1037
          build.collect_accessibility_reports!(accessibility_reports)
        end
      end
    end

1038 1039
    def coverage_reports
      Gitlab::Ci::Reports::CoverageReports.new.tap do |coverage_reports|
1040
        latest_report_builds(Ci::JobArtifact.coverage_reports).includes(:project).find_each do |build|
1041 1042 1043 1044 1045
          build.collect_coverage_reports!(coverage_reports)
        end
      end
    end

Maxime Orefice's avatar
Maxime Orefice committed
1046 1047 1048 1049 1050 1051 1052 1053
    def codequality_reports
      Gitlab::Ci::Reports::CodequalityReports.new.tap do |codequality_reports|
        latest_report_builds(Ci::JobArtifact.codequality_reports).each do |build|
          build.collect_codequality_reports!(codequality_reports)
        end
      end
    end

1054 1055
    def terraform_reports
      ::Gitlab::Ci::Reports::TerraformReports.new.tap do |terraform_reports|
1056
        latest_report_builds(::Ci::JobArtifact.terraform_reports).each do |build|
1057 1058 1059 1060 1061
          build.collect_terraform_reports!(terraform_reports)
        end
      end
    end

1062 1063 1064 1065
    def has_archive_artifacts?
      complete? && builds.latest.with_existing_job_artifacts(Ci::JobArtifact.archive.or(Ci::JobArtifact.metadata)).exists?
    end

1066 1067 1068 1069
    def has_exposed_artifacts?
      complete? && builds.latest.with_exposed_artifacts.exists?
    end

1070 1071 1072 1073 1074 1075
    def branch_updated?
      strong_memoize(:branch_updated) do
        push_details.branch_updated?
      end
    end

1076 1077 1078 1079 1080
    # Returns the modified paths.
    #
    # The returned value is
    # * Array: List of modified paths that should be evaluated
    # * nil: Modified path can not be evaluated
1081
    def modified_paths
1082
      strong_memoize(:modified_paths) do
1083
        if merge_request?
1084 1085 1086 1087
          merge_request.modified_paths
        elsif branch_updated?
          push_details.modified_paths
        end
1088 1089 1090
      end
    end

1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
    def all_worktree_paths
      strong_memoize(:all_worktree_paths) do
        project.repository.ls_files(sha)
      end
    end

    def top_level_worktree_paths
      strong_memoize(:top_level_worktree_paths) do
        project.repository.tree(sha).blobs.map(&:path)
      end
    end

1103 1104 1105 1106
    def default_branch?
      ref == project.default_branch
    end

1107 1108
    def merge_request?
      merge_request_id.present?
1109 1110 1111
    end

    def detached_merge_request_pipeline?
1112
      merge_request? && target_sha.nil?
1113 1114
    end

1115 1116 1117 1118
    def legacy_detached_merge_request_pipeline?
      detached_merge_request_pipeline? && !merge_request_ref?
    end

1119
    def merge_request_pipeline?
1120
      merge_request? && target_sha.present?
1121 1122
    end

1123 1124 1125 1126
    def merge_request_ref?
      MergeRequest.merge_request_ref?(ref)
    end

1127 1128 1129 1130
    def matches_sha_or_source_sha?(sha)
      self.sha == sha || self.source_sha == sha
    end

1131 1132 1133 1134
    def triggered_by?(current_user)
      user == current_user
    end

1135
    def source_ref
1136
      if merge_request?
1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
        merge_request.source_branch
      else
        ref
      end
    end

    def source_ref_slug
      Gitlab::Utils.slugify(source_ref.to_s)
    end

1147 1148 1149 1150
    def find_stage_by_name!(name)
      stages.find_by!(name: name)
    end

1151
    def full_error_messages
1152 1153 1154
      errors ? errors.full_messages.to_sentence : ""
    end

1155
    def merge_request_event_type
1156
      return unless merge_request?
1157 1158

      strong_memoize(:merge_request_event_type) do
1159
        if merge_request_pipeline?
1160
          :merged_result
1161 1162
        elsif detached_merge_request_pipeline?
          :detached
1163 1164 1165 1166
        end
      end
    end

1167 1168 1169 1170
    def persistent_ref
      @persistent_ref ||= PersistentRef.new(pipeline: self)
    end

1171
    def cacheable?
1172 1173 1174 1175 1176
      !dangling?
    end

    def dangling?
      Enums::Ci::Pipeline.dangling_sources.key?(source.to_sym)
1177 1178
    end

1179 1180
    def source_ref_path
      if branch? || merge_request?
1181
        Gitlab::Git::BRANCH_REF_PREFIX + source_ref.to_s
1182
      elsif tag?
1183
        Gitlab::Git::TAG_REF_PREFIX + source_ref.to_s
1184 1185 1186
      end
    end

1187 1188 1189 1190 1191 1192
    # Set scheduling type of processables if they were created before scheduling_type
    # data was deployed (https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22246).
    def ensure_scheduling_type!
      processables.populate_scheduling_type!
    end

1193 1194 1195 1196
    def ensure_ci_ref!
      self.ci_ref = Ci::Ref.ensure_for(self)
    end

1197
    def base_and_ancestors(same_project: false)
1198 1199 1200
      # Without using `unscoped`, caller scope is also included into the query.
      # Using `unscoped` here will be redundant after Rails 6.1
      ::Gitlab::Ci::PipelineObjectHierarchy
1201
        .new(self.class.unscoped.where(id: id), options: { same_project: same_project })
1202 1203 1204
        .base_and_ancestors
    end

1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216
    # We need `base_and_ancestors` in a specific order to "break" when needed.
    # If we use `find_each`, then the order is broken.
    # rubocop:disable Rails/FindEach
    def reset_ancestor_bridges!
      base_and_ancestors.includes(:source_bridge).each do |pipeline|
        break unless pipeline.bridge_waiting?

        pipeline.source_bridge.pending!
      end
    end
    # rubocop:enable Rails/FindEach

1217 1218 1219 1220 1221
    # EE-only
    def merge_train_pipeline?
      false
    end

1222 1223 1224 1225 1226 1227 1228 1229 1230 1231
    def security_reports(report_types: [])
      reports_scope = report_types.empty? ? ::Ci::JobArtifact.security_reports : ::Ci::JobArtifact.security_reports(file_types: report_types)

      ::Gitlab::Ci::Reports::Security::Reports.new(self).tap do |security_reports|
        latest_report_builds(reports_scope).each do |build|
          build.collect_security_reports!(security_reports)
        end
      end
    end

1232 1233
    private

1234 1235 1236 1237 1238 1239
    def add_message(severity, content)
      return unless Gitlab::Ci::Features.store_pipeline_messages?(project)

      messages.build(severity: severity, content: content)
    end

1240
    def pipeline_data
1241 1242 1243
      strong_memoize(:pipeline_data) do
        Gitlab::DataBuilder::Pipeline.build(self)
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1244
    end
1245

1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261
    def merge_request_diff_sha
      return unless merge_request?

      if merge_request_pipeline?
        source_sha
      else
        sha
      end
    end

    def merge_request_diff
      return unless merge_request?

      merge_request.merge_request_diff_for(merge_request_diff_sha)
    end

1262 1263
    def push_details
      strong_memoize(:push_details) do
1264
        Gitlab::Git::Push.new(project, before_sha, sha, git_ref)
1265 1266 1267
      end
    end

1268
    def git_ref
1269
      strong_memoize(:git_ref) do
1270
        if merge_request?
1271 1272 1273 1274 1275 1276 1277 1278 1279 1280
          ##
          # In the future, we're going to change this ref to
          # merge request's merged reference, such as "refs/merge-requests/:iid/merge".
          # In order to do that, we have to update GitLab-Runner's source pulling
          # logic.
          # See https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1092
          Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
        else
          super
        end
1281 1282 1283
      end
    end

1284
    def keep_around_commits
1285
      return unless project
1286

1287
      project.repository.keep_around(self.sha, self.before_sha)
1288
    end
1289 1290
  end
end
1291

1292
Ci::Pipeline.prepend_if_ee('EE::Ci::Pipeline')