pipeline.rb 42.5 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
    include EachBatch
20
    include FastDestroyAll::Helpers
21

22 23
    MAX_OPEN_MERGE_REQUESTS_REFS = 4

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

30 31
    BridgeStatusError = Class.new(StandardError)

32 33
    sha_attribute :source_sha
    sha_attribute :target_sha
Kamil Trzcinski's avatar
WIP  
Kamil Trzcinski committed
34

35 36 37 38 39 40 41
    # 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

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

50 51 52 53 54 55 56 57 58 59
    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
60

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

78 79
    has_many :messages, class_name: 'Ci::PipelineMessage', inverse_of: :pipeline

Felipe Artur's avatar
Felipe Artur committed
80 81
    # Merge requests for which the current pipeline is running against
    # the merge request's latest commit.
82
    has_many :merge_requests_as_head_pipeline, foreign_key: "head_pipeline_id", class_name: 'MergeRequest'
83

84
    has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
85
    has_many :failed_builds, -> { latest.failed }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
86
    has_many :retryable_builds, -> { latest.failed_or_canceled.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build', inverse_of: :pipeline
87
    has_many :cancelable_statuses, -> { cancelable }, foreign_key: :commit_id, class_name: 'CommitStatus'
88 89
    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
90

91 92
    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'
93
    has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_pipeline_id
94

95
    has_one :source_pipeline, class_name: 'Ci::Sources::Pipeline', inverse_of: :pipeline
96

97 98
    has_one :chat_data, class_name: 'Ci::PipelineChatData'

99
    has_many :triggered_pipelines, through: :sourced_pipelines, source: :pipeline
100
    has_many :child_pipelines, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :sourced_pipelines, source: :pipeline
101
    has_one :triggered_by_pipeline, through: :source_pipeline, source: :source_pipeline
102
    has_one :parent_pipeline, -> { merge(Ci::Sources::Pipeline.same_project) }, through: :source_pipeline, source: :source_pipeline
103
    has_one :source_job, through: :source_pipeline, source: :source_job
104
    has_one :source_bridge, through: :source_pipeline, source: :source_bridge
105

106 107
    has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline

108
    has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult', foreign_key: :last_pipeline_id
109
    has_many :latest_builds_report_results, through: :latest_builds, source: :report_results
110
    has_many :pipeline_artifacts, class_name: 'Ci::PipelineArtifact', inverse_of: :pipeline, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
111

112 113
    accepts_nested_attributes_for :variables, reject_if: :persisted?

114
    delegate :full_path, to: :project, prefix: true
Douwe Maan's avatar
Douwe Maan committed
115

Douwe Maan's avatar
Douwe Maan committed
116 117
    validates :sha, presence: { unless: :importing? }
    validates :ref, presence: { unless: :importing? }
118
    validates :tag, inclusion: { in: [false], if: :merge_request? }
119 120 121 122 123

    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
124
    validates :status, presence: { unless: :importing? }
125
    validate :valid_commit_sha, unless: :importing?
Jasper Maes's avatar
Jasper Maes committed
126
    validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
127

128
    after_create :keep_around_commits, unless: :importing?
Kamil Trzcinski's avatar
Kamil Trzcinski committed
129

130 131
    use_fast_destroy :job_artifacts

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

136
    enum_with_nil config_source: Enums::Ci::Pipeline.config_sources
137

138
    # We use `Enums::Ci::Pipeline.failure_reasons` here so that EE can more easily
139
    # extend this `Hash` with new values.
140
    enum failure_reason: Enums::Ci::Pipeline.failure_reasons
141

142 143
    enum locked: { unlocked: 0, artifacts_locked: 1 }

144
    state_machine :status, initial: :created do
145
      event :enqueue do
146
        transition [:created, :manual, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending
147
        transition [:success, :failed, :canceled] => :running
148 149 150

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

153 154 155 156
      event :request_resource do
        transition any - [:waiting_for_resource] => :waiting_for_resource
      end

Tiger's avatar
Tiger committed
157 158 159 160
      event :prepare do
        transition any - [:preparing] => :preparing
      end

161
      event :run do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
162
        transition any - [:running] => :running
163 164
      end

165
      event :skip do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
166
        transition any - [:skipped] => :skipped
167 168 169
      end

      event :drop do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
170
        transition any - [:failed] => :failed
171 172
      end

173
      event :succeed do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
174
        transition any - [:success] => :success
175 176 177
      end

      event :cancel do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
178
        transition any - [:canceled] => :canceled
179 180
      end

181
      event :block do
182
        transition any - [:manual] => :manual
183 184
      end

Shinya Maeda's avatar
Shinya Maeda committed
185 186 187 188
      event :delay do
        transition any - [:scheduled] => :scheduled
      end

189 190 191 192
      # IMPORTANT
      # Do not add any operations to this state_machine
      # Create a separate worker for each new operation

193
      before_transition [:created, :waiting_for_resource, :preparing, :pending] => :running do |pipeline|
194
        pipeline.started_at = Time.current
195 196
      end

197
      before_transition any => [:success, :failed, :canceled] do |pipeline|
198
        pipeline.finished_at = Time.current
199 200 201
        pipeline.update_duration
      end

202 203 204 205
      before_transition any => [:manual] do |pipeline|
        pipeline.update_duration
      end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
206
      before_transition canceled: any - [:canceled] do |pipeline|
Lin Jen-Shin's avatar
Lin Jen-Shin committed
207 208 209
        pipeline.auto_canceled_by = nil
      end

210 211 212 213 214 215
      before_transition any => :failed do |pipeline, transition|
        transition.args.first.try do |reason|
          pipeline.failure_reason = reason
        end
      end

216
      after_transition [:created, :waiting_for_resource, :preparing, :pending] => :running do |pipeline|
217
        pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
218 219 220
      end

      after_transition any => [:success] do |pipeline|
221
        pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
222 223
      end

224
      after_transition [:created, :waiting_for_resource, :preparing, :pending, :running] => :success do |pipeline|
225 226 227
        # 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.
228
        pipeline.run_after_commit { Ci::DailyBuildGroupReportResultsWorker.perform_in(10.minutes, pipeline.id) }
229
      end
230 231

      after_transition do |pipeline, transition|
232 233 234
        next if transition.loopback?

        pipeline.run_after_commit do
235
          PipelineHooksWorker.perform_async(pipeline.id)
236
          ExpirePipelineCacheWorker.perform_async(pipeline.id)
237
        end
238
      end
239

240 241
      after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
        pipeline.run_after_commit do
242 243
          pipeline.persistent_ref.delete

244 245 246 247 248
          pipeline.all_merge_requests.each do |merge_request|
            next unless merge_request.auto_merge_enabled?

            AutoMergeProcessWorker.perform_async(merge_request.id)
          end
249 250 251 252

          if pipeline.auto_devops_source?
            self.class.auto_devops_pipelines_completed_total.increment(status: pipeline.status)
          end
253 254 255
        end
      end

256 257
      after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
        pipeline.run_after_commit do
258
          ::Ci::PipelineArtifacts::CoverageReportWorker.perform_async(pipeline.id)
259
          ::Ci::PipelineArtifacts::CreateQualityReportWorker.perform_async(pipeline.id)
260 261 262
        end
      end

263 264 265 266 267 268 269 270
      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

271 272 273 274 275 276 277 278
      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

279 280 281 282 283 284
      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

285
      after_transition any => [:success, :failed] do |pipeline|
286 287
        ref_status = pipeline.ci_ref&.update_status_by!(pipeline)

288
        pipeline.run_after_commit do
289
          PipelineNotificationWorker.perform_async(pipeline.id, ref_status: ref_status)
290
        end
291
      end
292 293

      after_transition any => [:failed] do |pipeline|
294 295
        pipeline.run_after_commit do
          ::Gitlab::Ci::Pipeline::Metrics.pipeline_failure_reason_counter.increment(reason: pipeline.failure_reason)
296

297 298
          AutoDevops::DisableWorker.perform_async(pipeline.id) if pipeline.auto_devops_source?
        end
299
      end
300 301
    end

302
    scope :internal, -> { where(source: internal_sources) }
303
    scope :no_child, -> { where.not(source: :parent_pipeline) }
304
    scope :ci_sources, -> { where(source: Enums::Ci::Pipeline.ci_sources.values) }
305
    scope :ci_branch_sources, -> { where(source: Enums::Ci::Pipeline.ci_branch_sources.values) }
306
    scope :ci_and_parent_sources, -> { where(source: Enums::Ci::Pipeline.ci_and_parent_sources.values) }
307
    scope :for_user, -> (user) { where(user: user) }
308 309 310
    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)) }
311
    scope :for_ref, -> (ref) { where(ref: ref) }
312
    scope :for_branch, -> (branch) { for_ref(branch).where(tag: false) }
313
    scope :for_id, -> (id) { where(id: id) }
314
    scope :for_iid, -> (iid) { where(iid: iid) }
315
    scope :for_project, -> (project_id) { where(project_id: project_id) }
316
    scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
317 318
    scope :created_before_id, -> (id) { where('ci_pipelines.id < ?', id) }
    scope :before_pipeline, -> (pipeline) { created_before_id(pipeline.id).outside_pipeline_family(pipeline) }
319
    scope :eager_load_project, -> { eager_load(project: [:route, { namespace: :route }]) }
320 321 322 323

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

325 326 327 328
    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

329
    scope :with_only_interruptible_builds, -> do
330 331 332 333 334 335 336
      where('NOT EXISTS (?)',
        Ci::Build.where('ci_builds.commit_id = ci_pipelines.id')
                 .with_status(:running, :success, :failed)
                 .not_interruptible
      )
    end

337 338 339 340
    # 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
341 342 343
      where(source: :merge_request_event,
            merge_request: merge_request,
            project: [merge_request.source_project, merge_request.target_project])
344 345
    end

346 347 348 349 350
    # 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.
351
    # sha - The commit SHA (or mutliple SHAs) to limit the list of pipelines to.
352
    # limit - This limits a backlog search, default to 100.
353
    def self.newest_first(ref: nil, sha: nil, limit: 100)
354
      relation = order(id: :desc)
355
      relation = relation.where(ref: ref) if ref
356
      relation = relation.where(sha: sha) if sha
357

Kamil Trzciński's avatar
Kamil Trzciński committed
358 359 360 361
      if limit
        ids = relation.limit(limit).select(:id)
        relation = relation.where(id: ids)
      end
Kamil Trzciński's avatar
Kamil Trzciński committed
362

363
      relation
364
    end
365

366
    def self.latest_status(ref = nil)
367
      newest_first(ref: ref).pluck(:status).first
368 369
    end

370
    def self.latest_successful_for_ref(ref)
371
      newest_first(ref: ref).success.take
372 373
    end

374 375 376 377
    def self.latest_successful_for_sha(sha)
      newest_first(sha: sha).success.take
    end

378
    def self.latest_successful_for_refs(refs)
379
      relation = newest_first(ref: refs).success
380 381

      relation.each_with_object({}) do |pipeline, hash|
382 383 384 385
        hash[pipeline.ref] ||= pipeline
      end
    end

386 387 388 389 390 391 392 393
    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

394
    # Returns a Hash containing the latest pipeline for every given
395 396
    # commit.
    #
397
    # The keys of this Hash are the commit SHAs, the values the pipelines.
398
    #
399
    # commits - The list of commit SHAs to get the pipelines for.
400
    # ref - The ref to scope the data to (e.g. "master"). If the ref is not
401 402
    #       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
403
    def self.latest_pipeline_per_commit(commits, ref = nil)
404 405 406
      sql = select('DISTINCT ON (sha) *')
              .where(sha: commits)
              .order(:sha, id: :desc)
407

408
      sql = sql.where(ref: ref) if ref
409

410
      sql.each_with_object({}) do |pipeline, hash|
Kamil Trzciński's avatar
Kamil Trzciński committed
411
        hash[pipeline.sha] = pipeline
412 413 414
      end
    end

415 416 417 418
    def self.latest_successful_ids_per_project
      success.group(:project_id).select('max(id) as id')
    end

419 420 421 422
    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

423 424 425 426
    def self.truncate_sha(sha)
      sha[0...8]
    end

427
    def self.total_duration
Lin Jen-Shin's avatar
Lin Jen-Shin committed
428
      where.not(duration: nil).sum(:duration)
429 430
    end

431 432 433 434
    def self.internal_sources
      sources.reject { |source| source == "external" }.values
    end

435
    def self.bridgeable_statuses
436
      ::Ci::Pipeline::AVAILABLE_STATUSES - %w[created waiting_for_resource preparing pending]
437 438
    end

439 440 441 442
    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

443 444 445 446
    def uses_needs?
      builds.where(scheduling_type: :dag).any?
    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
    end

512 513 514 515 516 517
    def git_author_full_text
      strong_memoize(:git_author_full_text) do
        commit.try(:author_full_text)
      end
    end

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

524
    def git_commit_title
525 526 527
      strong_memoize(:git_commit_title) do
        commit.try(:title)
      end
528 529
    end

530
    def git_commit_full_title
531 532 533
      strong_memoize(:git_commit_full_title) do
        commit.try(:full_title)
      end
534 535 536
    end

    def git_commit_description
537 538 539
      strong_memoize(:git_commit_description) do
        commit.try(:description)
      end
540 541
    end

542 543 544 545 546 547
    def git_commit_timestamp
      strong_memoize(:git_commit_timestamp) do
        commit.try(:timestamp)
      end
    end

548 549 550 551
    def before_sha
      super || Gitlab::Git::BLANK_SHA
    end

552
    def short_sha
553
      Ci::Pipeline.truncate_sha(sha)
554 555
    end

556 557 558 559
    # NOTE: This is loaded lazily and will never be nil, even if the commit
    # cannot be found.
    #
    # Use constructs like: `pipeline.commit.present?`
560
    def commit
561
      @commit ||= Commit.lazy(project, sha)
562 563
    end

564
    def stuck?
565
      pending_builds.any?(&:stuck?)
566 567
    end

568
    def retryable?
569
      retryable_builds.any?
570 571
    end

572
    def cancelable?
573
      cancelable_statuses.any?
574 575
    end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
576 577 578 579
    def auto_canceled?
      canceled? && auto_canceled_by_id?
    end

580
    def cancel_running(retries: nil)
581 582 583 584 585 586 587 588 589 590 591 592
      commit_status_relations = [:project, :pipeline]
      ci_build_relations = [:deployment, :taggings]

      retry_optimistic_lock(cancelable_statuses, retries, name: 'ci_pipeline_cancel_running') do |cancelables|
        cancelables.find_in_batches do |batch|
          ActiveRecord::Associations::Preloader.new.preload(batch, commit_status_relations)
          ActiveRecord::Associations::Preloader.new.preload(batch.select { |job| job.is_a?(Ci::Build) }, ci_build_relations)

          batch.each do |job|
            yield(job) if block_given?
            job.cancel
          end
593
        end
Lin Jen-Shin's avatar
Lin Jen-Shin committed
594
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
595 596
    end

597
    def auto_cancel_running(pipeline, retries: nil)
598 599
      update(auto_canceled_by: pipeline)

600
      cancel_running(retries: retries) do |job|
601
        job.auto_canceled_by = pipeline
602
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
603 604
    end

605
    # rubocop: disable CodeReuse/ServiceClass
606
    def retry_failed(current_user)
607 608
      Ci::RetryPipelineService.new(project, current_user)
        .execute(self)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
609
    end
610
    # rubocop: enable CodeReuse/ServiceClass
Kamil Trzcinski's avatar
Kamil Trzcinski committed
611

612 613 614 615 616 617 618
    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|
619
          commits.each { |key, commit| loader.call(key, commits[key]) }
620 621 622 623
        end
      end
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
624
    def latest?
625
      return false unless git_ref && commit.present?
626

627 628 629 630 631 632 633
      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
634 635
    end

636
    def retried
637
      @retried ||= (statuses.order(id: :desc) - latest_statuses)
638 639 640
    end

    def coverage
641
      coverage_array = latest_statuses.map(&:coverage).compact
642 643
      if coverage_array.size >= 1
        '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
644 645 646
      end
    end

647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
    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
        ::Ci::JobArtifact.where(
          id: job_artifacts.with_reports
            .select('max(ci_job_artifacts.id) as id')
            .group(:file_type)
        )
          .preload(:job)
          .group_by(&:file_type)
      end
    end

673
    def has_kubernetes_active?
674 675 676
      strong_memoize(:has_kubernetes_active) do
        project.deployment_platform&.active?
      end
677 678
    end

679 680 681 682
    def freeze_period?
      Ci::FreezePeriodStatus.new(project: project).execute
    end

Connor Shea's avatar
Connor Shea committed
683
    def has_warnings?
684
      number_of_warnings > 0
685 686 687 688
    end

    def number_of_warnings
      BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader|
Cong Chen's avatar
Cong Chen committed
689
        ::CommitStatus.where(commit_id: pipeline_ids)
690 691 692 693 694 695
          .latest
          .failed_but_allowed
          .group(:commit_id)
          .count
          .each { |id, amount| loader.call(id, amount) }
      end
696 697
    end

698 699 700 701 702 703 704
    def needs_processing?
      statuses
        .where(processed: [false, nil])
        .latest
        .exists?
    end

705 706 707 708
    def has_yaml_errors?
      yaml_errors.present?
    end

709
    def add_error_message(content)
710 711 712 713 714 715 716 717 718 719 720 721 722
      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
723

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

James Lopez's avatar
James Lopez committed
730 731 732
    # 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
733
    # them using the +Gitlab::ImportExport::Project::RelationFactory+ class.
James Lopez's avatar
James Lopez committed
734 735 736 737 738 739 740 741 742
    def notes=(notes)
      notes.each do |note|
        note[:id] = nil
        note[:commit_id] = sha
        note[:noteable_id] = self['id']
        note.save!
      end
    end

743
    def notes
744
      project.notes.for_commit_id(sha)
745 746
    end

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

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

    def legacy_trigger
773
      strong_memoize(:legacy_trigger) { trigger_requests.first }
774 775
    end

776 777
    def persisted_variables
      Gitlab::Ci::Variables::Collection.new.tap do |variables|
778 779 780 781
        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))
782 783 784
      end
    end

785
    def predefined_variables
786 787 788
      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)
789
        variables.append(key: 'CI_PIPELINE_CREATED_AT', value: created_at&.iso8601)
790 791

        variables.concat(predefined_commit_variables)
792

793
        if merge_request?
794
          variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s)
795 796
          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)
797

798 799 800 801
          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)
802 803
          end

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

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

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

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

820 821 822 823 824 825 826
    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)
827
        variables.append(key: 'CI_COMMIT_BRANCH', value: ref) if branch?
828 829 830 831 832
        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)
833
        variables.append(key: 'CI_COMMIT_TIMESTAMP', value: git_commit_timestamp.to_s)
834
        variables.append(key: 'CI_COMMIT_AUTHOR', value: git_author_full_text.to_s)
835 836 837 838 839 840 841 842 843 844

        # 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

845 846 847 848
    def queued_duration
      return unless started_at

      seconds = (started_at - created_at).to_i
849
      seconds unless seconds == 0
850 851
    end

852
    def update_duration
853 854
      return unless started_at

855
      self.duration = Gitlab::Ci::Pipeline::Duration.from_pipeline(self)
856 857 858
    end

    def execute_hooks
859 860
      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)
861 862
    end

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

874 875 876 877
    def all_merge_requests_by_recency
      all_merge_requests.order(id: :desc)
    end

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 906 907
    # 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

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

914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
    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

933 934 935 936 937 938 939 940 941
    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

942 943 944 945 946 947
    def self_with_ancestors_and_descendants(same_project: false)
      ::Gitlab::Ci::PipelineObjectHierarchy
        .new(self.class.unscoped.where(id: id), options: { same_project: same_project })
        .all_objects
    end

948 949 950 951 952 953 954 955
    def bridge_triggered?
      source_bridge.present?
    end

    def bridge_waiting?
      source_bridge&.dependent?
    end

956
    def child?
957 958
      parent_pipeline? && # child pipelines have `parent_pipeline` source
        parent_pipeline.present?
959 960 961 962 963 964
    end

    def parent?
      child_pipelines.exists?
    end

965 966 967 968
    def created_successfully?
      persisted? && failure_reason.blank?
    end

969
    def detailed_status(current_user)
970
      Gitlab::Ci::Status::Pipeline::Factory
971
        .new(self.present, current_user)
972
        .fabricate!
973 974
    end

975
    def find_job_with_archive_artifacts(name)
976
      builds.latest.with_downloadable_artifacts.find_by_name(name)
977 978
    end

979
    def latest_builds_with_artifacts
980 981 982
      # 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.
983
      @latest_builds_with_artifacts ||= builds.latest.with_artifacts_not_expired.to_a
984 985
    end

986 987 988 989
    def latest_report_builds(reports_scope = ::Ci::JobArtifact.with_reports)
      builds.latest.with_reports(reports_scope)
    end

990 991 992 993
    def latest_test_report_builds
      latest_report_builds(Ci::JobArtifact.test_reports).preload(:project)
    end

994
    def builds_with_coverage
995
      builds.latest.with_coverage
996 997
    end

998 999 1000 1001
    def builds_with_failed_tests(limit: nil)
      latest_test_report_builds.failed.limit(limit)
    end

1002
    def has_reports?(reports_scope)
1003
      complete? && latest_report_builds(reports_scope).exists?
1004 1005
    end

1006
    def has_coverage_reports?
1007
      pipeline_artifacts&.report_exists?(:code_coverage)
1008 1009 1010 1011
    end

    def can_generate_coverage_reports?
      has_reports?(Ci::JobArtifact.coverage_reports)
1012 1013
    end

1014
    def has_codequality_mr_diff_report?
1015
      pipeline_artifacts&.report_exists?(:code_quality_mr_diff)
1016 1017 1018
    end

    def can_generate_codequality_reports?
1019
      return false unless ::Gitlab::Ci::Features.display_quality_on_mr_diff?(project)
1020

1021 1022 1023
      has_reports?(Ci::JobArtifact.codequality_reports)
    end

1024
    def test_report_summary
1025 1026 1027
      strong_memoize(:test_report_summary) do
        Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)
      end
1028 1029
    end

1030 1031
    def test_reports
      Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
1032
        latest_test_report_builds.find_each do |build|
1033 1034 1035 1036 1037
          build.collect_test_reports!(test_reports)
        end
      end
    end

1038 1039
    def accessibility_reports
      Gitlab::Ci::Reports::AccessibilityReports.new.tap do |accessibility_reports|
1040
        latest_report_builds(Ci::JobArtifact.accessibility_reports).each do |build|
1041 1042 1043 1044 1045
          build.collect_accessibility_reports!(accessibility_reports)
        end
      end
    end

1046 1047
    def coverage_reports
      Gitlab::Ci::Reports::CoverageReports.new.tap do |coverage_reports|
1048
        latest_report_builds(Ci::JobArtifact.coverage_reports).includes(:project).find_each do |build|
1049 1050 1051 1052 1053
          build.collect_coverage_reports!(coverage_reports)
        end
      end
    end

Maxime Orefice's avatar
Maxime Orefice committed
1054 1055 1056 1057 1058 1059 1060 1061
    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

1062 1063
    def terraform_reports
      ::Gitlab::Ci::Reports::TerraformReports.new.tap do |terraform_reports|
1064
        latest_report_builds(::Ci::JobArtifact.terraform_reports).each do |build|
1065 1066 1067 1068 1069
          build.collect_terraform_reports!(terraform_reports)
        end
      end
    end

1070 1071 1072 1073
    def has_archive_artifacts?
      complete? && builds.latest.with_existing_job_artifacts(Ci::JobArtifact.archive.or(Ci::JobArtifact.metadata)).exists?
    end

1074 1075 1076 1077
    def has_exposed_artifacts?
      complete? && builds.latest.with_exposed_artifacts.exists?
    end

1078 1079 1080 1081 1082 1083
    def branch_updated?
      strong_memoize(:branch_updated) do
        push_details.branch_updated?
      end
    end

1084 1085 1086 1087 1088
    # Returns the modified paths.
    #
    # The returned value is
    # * Array: List of modified paths that should be evaluated
    # * nil: Modified path can not be evaluated
1089
    def modified_paths
1090
      strong_memoize(:modified_paths) do
1091
        if merge_request?
1092 1093 1094
          merge_request.modified_paths
        elsif branch_updated?
          push_details.modified_paths
1095 1096
        elsif external_pull_request? && ::Feature.enabled?(:ci_modified_paths_of_external_prs, project, default_enabled: :yaml)
          external_pull_request.modified_paths
1097
        end
1098 1099 1100
      end
    end

1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112
    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

1113 1114 1115 1116
    def default_branch?
      ref == project.default_branch
    end

1117 1118
    def merge_request?
      merge_request_id.present?
1119 1120
    end

1121 1122 1123 1124
    def external_pull_request?
      external_pull_request_id.present?
    end

1125
    def detached_merge_request_pipeline?
1126
      merge_request? && target_sha.nil?
1127 1128
    end

1129 1130 1131 1132
    def legacy_detached_merge_request_pipeline?
      detached_merge_request_pipeline? && !merge_request_ref?
    end

1133
    def merged_result_pipeline?
1134
      merge_request? && target_sha.present?
1135 1136
    end

1137 1138 1139 1140
    def merge_request_ref?
      MergeRequest.merge_request_ref?(ref)
    end

1141 1142 1143 1144
    def matches_sha_or_source_sha?(sha)
      self.sha == sha || self.source_sha == sha
    end

1145 1146 1147 1148
    def triggered_by?(current_user)
      user == current_user
    end

1149
    def source_ref
1150
      if merge_request?
1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
        merge_request.source_branch
      else
        ref
      end
    end

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

1161 1162 1163 1164
    def find_stage_by_name!(name)
      stages.find_by!(name: name)
    end

1165
    def full_error_messages
1166 1167 1168
      errors ? errors.full_messages.to_sentence : ""
    end

1169
    def merge_request_event_type
1170
      return unless merge_request?
1171 1172

      strong_memoize(:merge_request_event_type) do
1173
        if merged_result_pipeline?
1174
          :merged_result
1175 1176
        elsif detached_merge_request_pipeline?
          :detached
1177 1178 1179 1180
        end
      end
    end

1181 1182 1183 1184
    def persistent_ref
      @persistent_ref ||= PersistentRef.new(pipeline: self)
    end

1185 1186
    def dangling?
      Enums::Ci::Pipeline.dangling_sources.key?(source.to_sym)
1187 1188
    end

1189 1190
    def source_ref_path
      if branch? || merge_request?
1191
        Gitlab::Git::BRANCH_REF_PREFIX + source_ref.to_s
1192
      elsif tag?
1193
        Gitlab::Git::TAG_REF_PREFIX + source_ref.to_s
1194 1195 1196
      end
    end

1197 1198 1199 1200 1201 1202
    # 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

1203 1204 1205 1206
    def ensure_ci_ref!
      self.ci_ref = Ci::Ref.ensure_for(self)
    end

1207
    def base_and_ancestors(same_project: false)
1208 1209 1210
      # Without using `unscoped`, caller scope is also included into the query.
      # Using `unscoped` here will be redundant after Rails 6.1
      ::Gitlab::Ci::PipelineObjectHierarchy
1211
        .new(self.class.unscoped.where(id: id), options: { same_project: same_project })
1212 1213 1214
        .base_and_ancestors
    end

1215 1216 1217
    # 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
1218 1219 1220
    def reset_source_bridge!(current_user)
      if ::Feature.enabled?(:ci_reset_bridge_with_subsequent_jobs, project, default_enabled: :yaml)
        return unless bridge_waiting?
1221

1222 1223 1224 1225 1226 1227 1228 1229
        source_bridge.pending!
        Ci::AfterRequeueJobService.new(project, current_user).execute(source_bridge) # rubocop:disable CodeReuse/ServiceClass
      else
        base_and_ancestors.includes(:source_bridge).each do |pipeline|
          break unless pipeline.bridge_waiting?

          pipeline.source_bridge.pending!
        end
1230 1231 1232 1233
      end
    end
    # rubocop:enable Rails/FindEach

1234 1235 1236 1237 1238
    # EE-only
    def merge_train_pipeline?
      false
    end

1239 1240 1241 1242 1243 1244 1245 1246 1247 1248
    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

1249 1250
    private

1251 1252 1253 1254
    def add_message(severity, content)
      messages.build(severity: severity, content: content)
    end

1255
    def pipeline_data
1256 1257 1258
      strong_memoize(:pipeline_data) do
        Gitlab::DataBuilder::Pipeline.build(self)
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1259
    end
1260

1261 1262 1263
    def merge_request_diff_sha
      return unless merge_request?

1264
      if merged_result_pipeline?
1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276
        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

1277 1278
    def push_details
      strong_memoize(:push_details) do
1279
        Gitlab::Git::Push.new(project, before_sha, sha, git_ref)
1280 1281 1282
      end
    end

1283
    def git_ref
1284
      strong_memoize(:git_ref) do
1285
        if merge_request?
1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
          ##
          # 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
1296 1297 1298
      end
    end

1299
    def keep_around_commits
1300
      return unless project
1301

1302
      project.repository.keep_around(self.sha, self.before_sha)
1303
    end
1304 1305
  end
end
1306

1307
Ci::Pipeline.prepend_mod_with('Ci::Pipeline')