merge_request.rb 40.2 KB
Newer Older
1 2
# frozen_string_literal: true

3
class MergeRequest < ApplicationRecord
4
  include AtomicInternalId
S
Shinya Maeda 已提交
5
  include IidRoutes
6
  include Issuable
7
  include Noteable
8
  include Referable
9
  include Presentable
10
  include IgnorableColumn
11
  include TimeTrackable
12 13
  include ManualInverseAssociation
  include EachBatch
14
  include ThrottledTouch
15
  include Gitlab::Utils::StrongMemoize
J
Jan Provaznik 已提交
16
  include LabelEventable
S
Shinya Maeda 已提交
17
  include ReactiveCaching
18
  include FromUnion
S
Shinya Maeda 已提交
19 20

  self.reactive_cache_key = ->(model) { [model.project.id, model.iid] }
S
Shinya Maeda 已提交
21 22
  self.reactive_cache_refresh_interval = 10.minutes
  self.reactive_cache_lifetime = 10.minutes
23

24 25
  SORTING_PREFERENCE_FIELD = :merge_requests_sort

26
  ignore_column :locked_at,
27 28
                :ref_fetched,
                :deleted_at
29

30 31
  belongs_to :target_project, class_name: "Project"
  belongs_to :source_project, class_name: "Project"
Z
Zeger-Jan van de Weg 已提交
32
  belongs_to :merge_user, class_name: "User"
33

34 35
  has_internal_id :iid, scope: :target_project, init: ->(s) { s&.target_project&.merge_requests&.maximum(:iid) }

36
  has_many :merge_request_diffs
37

38
  has_one :merge_request_diff,
39
    -> { order('merge_request_diffs.id DESC') }, inverse_of: :merge_request
40

41 42 43 44 45 46 47 48 49 50 51 52
  belongs_to :latest_merge_request_diff, class_name: 'MergeRequestDiff'
  manual_inverse_association :latest_merge_request_diff, :merge_request

  # This is the same as latest_merge_request_diff unless:
  # 1. There are arguments - in which case we might be trying to force-reload.
  # 2. This association is already loaded.
  # 3. The latest diff does not exist.
  #
  # The second one in particular is important - MergeRequestDiff#merge_request
  # is the inverse of MergeRequest#merge_request_diff, which means it may not be
  # the latest diff, because we could have loaded any diff from this particular
  # MR. If we haven't already loaded a diff, then it's fine to load the latest.
53 54
  def merge_request_diff
    fallback = latest_merge_request_diff unless association(:merge_request_diff).loaded?
55 56 57 58

    fallback || super
  end

59 60
  belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline"

61
  has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
62

63 64 65
  has_many :merge_requests_closing_issues,
    class_name: 'MergeRequestsClosingIssues',
    dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
66

67
  has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue
S
Shinya Maeda 已提交
68
  has_many :merge_request_pipelines, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline'
69
  has_many :suggestions, through: :notes
70

71 72
  has_many :merge_request_assignees
  # Will be deprecated at https://gitlab.com/gitlab-org/gitlab-ce/issues/59457
73 74
  belongs_to :assignee, class_name: "User"

75
  serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
Z
Zeger-Jan van de Weg 已提交
76

77
  after_create :ensure_merge_request_diff
78
  after_update :clear_memoized_shas
79
  after_update :reload_diff_if_branch_changed
80
  after_save :ensure_metrics
81

82 83 84 85
  # Required until the codebase starts using this relation for single or multiple assignees.
  # TODO: Remove at gitlab-ee#2004 implementation.
  after_save :refresh_merge_request_assignees, if: :assignee_id_changed?

D
Dmitriy Zaporozhets 已提交
86 87 88 89
  # When this attribute is true some MR validation is ignored
  # It allows us to close or modify broken merge requests
  attr_accessor :allow_broken

D
Dmitriy Zaporozhets 已提交
90 91
  # Temporary fields to store compare vars
  # when creating new merge request
92
  attr_accessor :can_be_created, :compare_commits, :diff_options, :compare
D
Dmitriy Zaporozhets 已提交
93

A
Andrew8xx8 已提交
94
  state_machine :state, initial: :opened do
A
Andrew8xx8 已提交
95
    event :close do
96
      transition [:opened] => :closed
A
Andrew8xx8 已提交
97 98
    end

99
    event :mark_as_merged do
100
      transition [:opened, :locked] => :merged
A
Andrew8xx8 已提交
101 102 103
    end

    event :reopen do
104
      transition closed: :opened
A
Andrew8xx8 已提交
105 106
    end

107
    event :lock_mr do
108
      transition [:opened] => :locked
D
Dmitriy Zaporozhets 已提交
109 110
    end

111
    event :unlock_mr do
112
      transition locked: :opened
D
Dmitriy Zaporozhets 已提交
113 114
    end

115 116
    before_transition any => :opened do |merge_request|
      merge_request.merge_jid = nil
117
    end
118

119
    after_transition any => :opened do |merge_request|
120 121 122 123 124
      merge_request.run_after_commit do
        UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
      end
    end

A
Andrew8xx8 已提交
125 126 127
    state :opened
    state :closed
    state :merged
D
Dmitriy Zaporozhets 已提交
128
    state :locked
A
Andrew8xx8 已提交
129 130
  end

131 132
  state_machine :merge_status, initial: :unchecked do
    event :mark_as_unchecked do
133 134
      transition [:can_be_merged, :unchecked] => :unchecked
      transition [:cannot_be_merged, :cannot_be_merged_recheck] => :cannot_be_merged_recheck
135 136 137
    end

    event :mark_as_mergeable do
138
      transition [:unchecked, :cannot_be_merged_recheck] => :can_be_merged
139 140 141
    end

    event :mark_as_unmergeable do
142
      transition [:unchecked, :cannot_be_merged_recheck] => :cannot_be_merged
143 144
    end

145
    state :unchecked
146
    state :cannot_be_merged_recheck
147 148
    state :can_be_merged
    state :cannot_be_merged
149 150

    around_transition do |merge_request, transition, block|
151
      Gitlab::Timeless.timeless(merge_request, &block)
152
    end
153

154
    # rubocop: disable CodeReuse/ServiceClass
155
    after_transition unchecked: :cannot_be_merged do |merge_request, transition|
156 157 158
      if merge_request.notify_conflict?
        NotificationService.new.merge_request_unmergeable(merge_request)
        TodoService.new.merge_request_became_unmergeable(merge_request)
159
      end
160
    end
161
    # rubocop: enable CodeReuse/ServiceClass
162

163 164 165
    def check_state?(merge_status)
      [:unchecked, :cannot_be_merged_recheck].include?(merge_status.to_sym)
    end
166
  end
167

168
  validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
A
Andrey Kumanyaev 已提交
169
  validates :source_branch, presence: true
I
Izaak Alpert 已提交
170
  validates :target_project, presence: true
A
Andrey Kumanyaev 已提交
171
  validates :target_branch, presence: true
J
James Lopez 已提交
172
  validates :merge_user, presence: true, if: :merge_when_pipeline_succeeds?, unless: :importing?
173 174
  validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
  validate :validate_fork, unless: :closed_without_fork?
175
  validate :validate_target_project, on: :create
D
Dmitriy Zaporozhets 已提交
176

177 178 179
  scope :by_source_or_target_branch, ->(branch_name) do
    where("source_branch = :branch OR target_branch = :branch", branch: branch_name)
  end
180
  scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
181
  scope :of_projects, ->(ids) { where(target_project_id: ids) }
182
  scope :from_project, ->(project) { where(source_project_id: project.id) }
183 184
  scope :merged, -> { with_state(:merged) }
  scope :closed_and_merged, -> { with_states(:closed, :merged) }
S
Scott Le 已提交
185
  scope :from_source_branches, ->(branches) { where(source_branch: branches) }
186 187 188
  scope :by_commit_sha, ->(sha) do
    where('EXISTS (?)', MergeRequestDiff.select(1).where('merge_requests.latest_merge_request_diff_id = merge_request_diffs.id').by_commit_sha(sha)).reorder(nil)
  end
189 190
  scope :join_project, -> { joins(:target_project) }
  scope :references_project, -> { references(:target_project) }
191 192 193
  scope :assigned, -> { where("assignee_id IS NOT NULL") }
  scope :unassigned, -> { where("assignee_id IS NULL") }
  scope :assigned_to, ->(u) { where(assignee_id: u.id)}
194 195 196 197 198 199 200
  scope :with_api_entity_associations, -> {
    preload(:author, :assignee, :notes, :labels, :milestone, :timelogs,
            latest_merge_request_diff: [:merge_request_diff_commits],
            metrics: [:latest_closed_by, :merged_by],
            target_project: [:route, { namespace: :route }],
            source_project: [:route, { namespace: :route }])
  }
201 202

  participant :assignee
203

204 205
  after_save :keep_around_commit

206 207 208
  alias_attribute :project, :target_project
  alias_attribute :project_id, :target_project_id

209 210 211 212
  def self.reference_prefix
    '!'
  end

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
  # Returns the top 100 target branches
  #
  # The returned value is a Array containing branch names
  # sort by updated_at of merge request:
  #
  #     ['master', 'develop', 'production']
  #
  # limit - The maximum number of target branch to return.
  def self.recent_target_branches(limit: 100)
    group(:target_branch)
      .select(:target_branch)
      .reorder('MAX(merge_requests.updated_at) DESC')
      .limit(limit)
      .pluck(:target_branch)
  end

229
  def rebase_in_progress?
230 231 232
    strong_memoize(:rebase_in_progress) do
      # The source project can be deleted
      next false unless source_project
233

234 235
      source_project.repository.rebase_in_progress?(id)
    end
236 237
  end

238 239 240
  # Use this method whenever you need to make sure the head_pipeline is synced with the
  # branch head commit, for example checking if a merge request can be merged.
  # For more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/40004
241
  def actual_head_pipeline
242
    head_pipeline&.matches_sha_or_source_sha?(diff_head_sha) ? head_pipeline : nil
243 244
  end

245 246 247 248 249 250
  def merge_pipeline
    return unless merged?

    target_project.pipeline_for(target_branch, merge_commit_sha)
  end

251 252 253 254
  # Pattern used to extract `!123` merge request references from text
  #
  # This pattern supports cross-project references.
  def self.reference_pattern
255
    @reference_pattern ||= %r{
256
      (#{Project.reference_pattern})?
257 258 259 260
      #{Regexp.escape(reference_prefix)}(?<merge_request>\d+)
    }x
  end

261
  def self.link_reference_pattern
262
    @link_reference_pattern ||= super("merge_requests", /(?<merge_request>\d+)/)
263 264
  end

265 266 267 268
  def self.reference_valid?(reference)
    reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
  end

269 270 271 272
  def self.project_foreign_key
    'target_project_id'
  end

273 274 275 276 277 278 279 280 281 282 283
  # Returns all the merge requests from an ActiveRecord:Relation.
  #
  # This method uses a UNION as it usually operates on the result of
  # ProjectsFinder#execute. PostgreSQL in particular doesn't always like queries
  # using multiple sub-queries especially when combined with an OR statement.
  # UNIONs on the other hand perform much better in these cases.
  #
  # relation - An ActiveRecord::Relation that returns a list of Projects.
  #
  # Returns an ActiveRecord::Relation.
  def self.in_projects(relation)
M
mhasbini 已提交
284 285
    # unscoping unnecessary conditions that'll be applied
    # when executing `where("merge_requests.id IN (#{union.to_sql})")`
286 287
    source = unscoped.where(source_project_id: relation)
    target = unscoped.where(target_project_id: relation)
288

289
    from_union([source, target])
290 291
  end

292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
  # This is used after project import, to reset the IDs to the correct
  # values. It is not intended to be called without having already scoped the
  # relation.
  def self.set_latest_merge_request_diff_ids!
    update = '
      latest_merge_request_diff_id = (
        SELECT MAX(id)
        FROM merge_request_diffs
        WHERE merge_requests.id = merge_request_diffs.merge_request_id
      )'.squish

    self.each_batch do |batch|
      batch.update_all(update)
    end
  end

308
  WIP_REGEX = /\A*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
T
Thomas Balthazar 已提交
309 310 311 312 313 314 315 316 317 318 319 320 321

  def self.work_in_progress?(title)
    !!(title =~ WIP_REGEX)
  end

  def self.wipless_title(title)
    title.sub(WIP_REGEX, "")
  end

  def self.wip_title(title)
    work_in_progress?(title) ? title : "WIP: #{title}"
  end

S
Stan Hu 已提交
322 323
  def commit_authors
    @commit_authors ||= commits.authors
324 325 326
  end

  def authors
S
Stan Hu 已提交
327
    User.from_union([commit_authors, User.where(id: self.author_id)])
328 329
  end

330 331 332 333 334 335
  # Verifies if title has changed not taking into account WIP prefix
  # for merge requests.
  def wipless_title_changed(old_title)
    self.class.wipless_title(old_title) != self.wipless_title
  end

336
  def hook_attrs
337
    Gitlab::HookData::MergeRequestBuilder.new(self).build
338 339
  end

340 341 342 343 344 345 346 347
  # Returns a Hash of attributes to be used for Twitter card metadata
  def card_attributes
    {
      'Author'   => author.try(:name),
      'Assignee' => assignee.try(:name)
    }
  end

348
  # These method are needed for compatibility with issues to not mess view and other code
349 350 351 352
  def assignees
    Array(assignee)
  end

353 354 355 356 357 358 359 360
  def assignee_ids
    Array(assignee_id)
  end

  def assignee_ids=(ids)
    write_attribute(:assignee_id, ids.last)
  end

361 362 363 364
  def assignee_or_author?(user)
    author_id == user.id || assignee_id == user.id
  end

365
  # `from` argument can be a Namespace or Project.
366
  def to_reference(from = nil, full: false)
367 368
    reference = "#{self.class.reference_prefix}#{iid}"

369
    "#{project.to_reference(from, full: full)}#{reference}"
370 371
  end

372
  def commits
373 374 375 376 377 378 379 380 381
    return merge_request_diff.commits if persisted?

    commits_arr = if compare_commits
                    compare_commits.reverse
                  else
                    []
                  end

    CommitCollection.new(source_project, commits_arr, source_branch)
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
  end

  def commits_count
    if persisted?
      merge_request_diff.commits_count
    elsif compare_commits
      compare_commits.size
    else
      0
    end
  end

  def commit_shas
    if persisted?
      merge_request_diff.commit_shas
    elsif compare_commits
398
      compare_commits.to_a.reverse.map(&:sha)
399
    else
400
      Array(diff_head_sha)
401 402 403
    end
  end

404 405 406 407 408 409 410 411 412
  # Returns true if there are commits that match at least one commit SHA.
  def includes_any_commits?(shas)
    if persisted?
      merge_request_diff.commits_by_shas(shas).exists?
    else
      (commit_shas & shas).present?
    end
  end

413
  def supports_suggestion?
414
    true
415 416
  end

417 418 419
  # Calls `MergeWorker` to proceed with the merge process and
  # updates `merge_jid` with the MergeWorker#jid.
  # This helps tracking enqueued and ongoing merge jobs.
420
  def merge_async(user_id, params)
421
    jid = MergeWorker.perform_async(id, user_id, params.to_h)
422 423 424
    update_column(:merge_jid, jid)
  end

L
lulalala 已提交
425 426 427 428 429 430 431 432 433 434
  def merge_participants
    participants = [author]

    if merge_when_pipeline_succeeds? && !participants.include?(merge_user)
      participants << merge_user
    end

    participants
  end

435 436
  def first_commit
    merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
437
  end
438

439
  def raw_diffs(*args)
440
    merge_request_diff ? merge_request_diff.raw_diffs(*args) : compare.raw_diffs(*args)
S
Sean McGivern 已提交
441 442
  end

443
  def diffs(diff_options = {})
444
    if compare
445
      # When saving MR diffs, `expanded` is implicitly added (because we need
446 447
      # to save the entire contents to the DB), so add that here for
      # consistency.
448
      compare.diffs(diff_options.merge(expanded: true))
449
    else
450
      merge_request_diff.diffs(diff_options)
451
    end
S
Sean McGivern 已提交
452 453
  end

454 455 456 457
  def non_latest_diffs
    merge_request_diffs.where.not(id: merge_request_diff.id)
  end

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
  def preloads_discussion_diff_highlighting?
    true
  end

  def preload_discussions_diff_highlight
    preloadable_files = note_diff_files.for_commit_or_unresolved

    discussions_diffs.load_highlight(preloadable_files.pluck(:id))
  end

  def discussions_diffs
    strong_memoize(:discussions_diffs) do
      Gitlab::DiscussionsDiff::FileCollection.new(note_diff_files.to_a)
    end
  end

  def note_diff_files
    NoteDiffFile
      .where(diff_note: discussion_notes)
      .includes(diff_note: :project)
  end

J
Jacob Vosmaer 已提交
480
  def diff_size
481 482
    # Calling `merge_request_diff.diffs.real_size` will also perform
    # highlighting, which we don't need here.
483
    merge_request_diff&.real_size || diffs.real_size
J
Jacob Vosmaer 已提交
484 485
  end

486 487 488 489 490 491 492 493 494 495 496 497
  def modified_paths(past_merge_request_diff: nil)
    diffs = if past_merge_request_diff
              past_merge_request_diff
            elsif compare
              compare
            else
              self.merge_request_diff
            end

    diffs.modified_paths
  end

498
  def diff_base_commit
499
    if persisted?
500
      merge_request_diff.base_commit
501 502
    else
      branch_merge_base_commit
503 504 505 506 507 508 509 510
    end
  end

  def diff_start_commit
    if persisted?
      merge_request_diff.start_commit
    else
      target_branch_head
511 512 513
    end
  end

514 515 516 517 518 519 520 521 522
  def diff_head_commit
    if persisted?
      merge_request_diff.head_commit
    else
      source_branch_head
    end
  end

  def diff_start_sha
523 524 525 526 527
    if persisted?
      merge_request_diff.start_commit_sha
    else
      target_branch_head.try(:sha)
    end
528 529 530
  end

  def diff_base_sha
531 532 533 534 535
    if persisted?
      merge_request_diff.base_commit_sha
    else
      branch_merge_base_commit.try(:sha)
    end
536 537 538
  end

  def diff_head_sha
539 540 541 542 543
    if persisted?
      merge_request_diff.head_commit_sha
    else
      source_branch_head.try(:sha)
    end
544 545 546 547 548 549 550 551
  end

  # When importing a pull request from GitHub, the old and new branches may no
  # longer actually exist by those names, but we need to recreate the merge
  # request diff with the right source and target shas.
  # We use these attributes to force these to the intended values.
  attr_writer :target_branch_sha, :source_branch_sha

552 553 554 555 556 557 558 559 560 561 562 563 564 565
  def source_branch_ref
    return @source_branch_sha if @source_branch_sha
    return unless source_branch

    Gitlab::Git::BRANCH_REF_PREFIX + source_branch
  end

  def target_branch_ref
    return @target_branch_sha if @target_branch_sha
    return unless target_branch

    Gitlab::Git::BRANCH_REF_PREFIX + target_branch
  end

566
  def source_branch_head
567 568 569 570 571
    strong_memoize(:source_branch_head) do
      if source_project && source_branch_ref
        source_project.repository.commit(source_branch_ref)
      end
    end
572 573 574
  end

  def target_branch_head
575 576 577
    strong_memoize(:target_branch_head) do
      target_project.repository.commit(target_branch_ref)
    end
578 579
  end

580 581 582 583 584 585 586 587 588
  def branch_merge_base_commit
    start_sha = target_branch_sha
    head_sha  = source_branch_sha

    if start_sha && head_sha
      target_project.merge_base_commit(start_sha, head_sha)
    end
  end

589
  def target_branch_sha
590
    @target_branch_sha || target_branch_head.try(:sha)
591 592 593
  end

  def source_branch_sha
594
    @source_branch_sha || source_branch_head.try(:sha)
595 596
  end

597
  def diff_refs
598 599 600 601 602 603 604 605 606 607 608 609 610
    persisted? ? merge_request_diff.diff_refs : repository_diff_refs
  end

  # Instead trying to fetch the
  # persisted diff_refs, this method goes
  # straight to the repository to get the
  # most recent data possible.
  def repository_diff_refs
    Gitlab::Diff::DiffRefs.new(
      base_sha:  branch_merge_base_sha,
      start_sha: target_branch_sha,
      head_sha:  source_branch_sha
    )
611 612
  end

613 614 615 616
  def branch_merge_base_sha
    branch_merge_base_commit.try(:sha)
  end

617
  def validate_branches
618
    if target_project == source_project && target_branch == source_branch
619 620
      errors.add :branch_conflict, "You can't use same project/branch for source and target"
      return
621
    end
622

623
    if opened?
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
      similar_mrs = target_project
        .merge_requests
        .where(source_branch: source_branch, target_branch: target_branch)
        .where(source_project_id: source_project&.id)
        .opened

      similar_mrs = similar_mrs.where.not(id: id) if persisted?

      conflict = similar_mrs.first

      if conflict.present?
        errors.add(
          :validate_branches,
          "Another open merge request already exists for this source branch: #{conflict.to_reference}"
        )
639
      end
640
    end
641 642
  end

643 644 645 646 647 648
  def validate_target_project
    return true if target_project.merge_requests_enabled?

    errors.add :base, 'Target project has disabled merge requests'
  end

649
  def validate_fork
650
    return true unless target_project && source_project
651
    return true if target_project == source_project
652
    return true unless source_project_missing?
653

654
    errors.add :validate_fork,
655
               'Source project is not a fork of the target project'
656 657
  end

658
  def merge_ongoing?
659 660
    # While the MergeRequest is locked, it should present itself as 'merge ongoing'.
    # The unlocking process is handled by StuckMergeJobsWorker scheduled in Cron.
661 662 663
    return true if locked?

    !!merge_jid && !merged? && Gitlab::SidekiqStatus.running?(merge_jid)
664 665
  end

666
  def closed_without_fork?
667
    closed? && source_project_missing?
668 669
  end

670
  def source_project_missing?
671 672 673
    return false unless for_fork?
    return true unless source_project

674
    !source_project.in_fork_network_of?(target_project)
675 676
  end

677
  def reopenable?
678
    closed? && !source_project_missing? && source_branch_exists?
K
Katarzyna Kobierska 已提交
679 680
  end

681 682
  def ensure_merge_request_diff
    merge_request_diff || create_merge_request_diff
683 684
  end

685 686 687 688 689 690 691 692 693
  def refresh_merge_request_assignees
    transaction do
      # Using it instead relation.delete_all in order to avoid adding a
      # dependent: :delete_all (we already have foreign key cascade deletion).
      MergeRequestAssignee.where(merge_request_id: self).delete_all
      merge_request_assignees.create(user_id: assignee_id) if assignee_id
    end
  end

694
  def create_merge_request_diff
695
    fetch_ref!
696

697 698 699 700 701
    # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37435
    Gitlab::GitalyClient.allow_n_plus_1_calls do
      merge_request_diffs.create
      reload_merge_request_diff
    end
702 703
  end

704 705 706 707
  def viewable_diffs
    @viewable_diffs ||= merge_request_diffs.viewable.to_a
  end

708
  def merge_request_diff_for(diff_refs_or_sha)
709 710 711 712 713 714 715 716 717 718
    matcher =
      if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs)
        {
          'start_commit_sha' => diff_refs_or_sha.start_sha,
          'head_commit_sha' => diff_refs_or_sha.head_sha,
          'base_commit_sha' => diff_refs_or_sha.base_sha
        }
      else
        { 'head_commit_sha' => diff_refs_or_sha }
      end
D
Douwe Maan 已提交
719

720 721 722
    viewable_diffs.find do |diff|
      diff.attributes.slice(*matcher.keys) == matcher
    end
723 724
  end

725 726 727 728 729 730 731 732 733 734 735
  def version_params_for(diff_refs)
    if diff = merge_request_diff_for(diff_refs)
      { diff_id: diff.id }
    elsif diff = merge_request_diff_for(diff_refs.head_sha)
      {
        diff_id: diff.id,
        start_sha: diff_refs.start_sha
      }
    end
  end

736 737 738 739 740 741 742
  def clear_memoized_shas
    @target_branch_sha = @source_branch_sha = nil

    clear_memoization(:source_branch_head)
    clear_memoization(:target_branch_head)
  end

743
  def reload_diff_if_branch_changed
744 745
    if (source_branch_changed? || target_branch_changed?) &&
        (source_branch_head && target_branch_head)
746
      reload_diff
D
Dmitriy Zaporozhets 已提交
747 748 749
    end
  end

750
  # rubocop: disable CodeReuse/ServiceClass
751
  def reload_diff(current_user = nil)
752 753
    return unless open?

754
    MergeRequests::ReloadDiffsService.new(self, current_user).execute
755
  end
756
  # rubocop: enable CodeReuse/ServiceClass
757

758
  def check_if_can_be_merged
759
    return unless self.class.state_machines[:merge_status].check_state?(merge_status) && Gitlab::Database.read_write?
760

761
    can_be_merged =
762
      !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch)
763 764

    if can_be_merged
765 766 767 768
      mark_as_mergeable
    else
      mark_as_unmergeable
    end
769 770
  end

D
Dmitriy Zaporozhets 已提交
771
  def merge_event
772
    @merge_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
D
Dmitriy Zaporozhets 已提交
773 774
  end

775
  def closed_event
776
    @closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
777 778
  end

779
  def work_in_progress?
T
Thomas Balthazar 已提交
780
    self.class.work_in_progress?(title)
781 782 783
  end

  def wipless_title
T
Thomas Balthazar 已提交
784 785 786 787 788
    self.class.wipless_title(self.title)
  end

  def wip_title
    self.class.wip_title(self.title)
789 790
  end

791 792
  def mergeable?(skip_ci_check: false)
    return false unless mergeable_state?(skip_ci_check: skip_ci_check)
793 794 795

    check_if_can_be_merged

796
    can_be_merged? && !should_be_rebased?
797 798
  end

799
  def mergeable_state?(skip_ci_check: false, skip_discussions_check: false)
800 801 802
    return false unless open?
    return false if work_in_progress?
    return false if broken?
803
    return false unless skip_ci_check || mergeable_ci_state?
804
    return false unless skip_discussions_check || mergeable_discussions_state?
805 806

    true
807 808
  end

809 810 811 812 813 814 815 816 817 818
  def mergeable_to_ref?
    return false if merged?
    return false if broken?

    # Given the `merge_ref_path` will have the same
    # state the `target_branch` would have. Ideally
    # we need to check if it can be merged to it.
    project.repository.can_be_merged?(diff_head_sha, target_branch)
  end

819 820 821 822 823 824 825 826
  def ff_merge_possible?
    project.repository.ancestor?(target_branch_sha, diff_head_sha)
  end

  def should_be_rebased?
    project.ff_merge_must_be_possible? && !ff_merge_possible?
  end

J
James Lopez 已提交
827
  def can_cancel_merge_when_pipeline_succeeds?(current_user)
828
    can_be_merged_by?(current_user) || self.author == current_user
829 830
  end

831
  def can_remove_source_branch?(current_user)
832
    !ProtectedBranch.protected?(source_project, source_branch) &&
833
      !source_project.root_ref?(source_branch) &&
H
http://jneen.net/ 已提交
834
      Ability.allowed?(current_user, :push_code, source_project) &&
835
      diff_head_sha == source_branch_head.try(:sha)
836 837
  end

838
  def should_remove_source_branch?
839
    Gitlab::Utils.to_boolean(merge_params['should_remove_source_branch'])
840 841 842
  end

  def force_remove_source_branch?
843
    Gitlab::Utils.to_boolean(merge_params['force_remove_source_branch'])
844 845 846 847 848 849
  end

  def remove_source_branch?
    should_remove_source_branch? || force_remove_source_branch?
  end

850
  def notify_conflict?
851 852 853 854 855 856 857 858
    (opened? || locked?) &&
      has_commits? &&
      !branch_missing? &&
      !project.repository.can_be_merged?(diff_head_sha, target_branch)
  rescue Gitlab::Git::CommandError
    # Checking mergeability can trigger exception, e.g. non-utf8
    # We ignore this type of errors.
    false
859 860
  end

861
  def related_notes
862 863
    # Fetch comments only from last 100 commits
    commits_for_notes_limit = 100
864
    commit_ids = commit_shas.take(commits_for_notes_limit)
865

866 867 868
    commit_notes = Note
      .except(:order)
      .where(project_id: [source_project_id, target_project_id])
869
      .for_commit_id(commit_ids)
870 871 872 873 874

    # We're using a UNION ALL here since this results in better performance
    # compared to using OR statements. We're using UNION ALL since the queries
    # used won't produce any duplicates (e.g. a note for a commit can't also be
    # a note for an MR).
875 876
    Note
      .from_union([notes, commit_notes], remove_duplicates: false)
877
      .includes(:noteable)
878
  end
879

880
  alias_method :discussion_notes, :related_notes
881

882 883 884
  def mergeable_discussions_state?
    return true unless project.only_allow_merge_if_all_discussions_are_resolved?

885
    !discussions_to_be_resolved?
886 887
  end

I
Izaak Alpert 已提交
888 889 890 891
  def for_fork?
    target_project != source_project
  end

892 893 894 895
  # If the merge request closes any issues, save this information in the
  # `MergeRequestsClosingIssues` model. This is a performance optimization.
  # Calculating this information for a number of merge requests requires
  # running `ReferenceExtractor` on each of them separately.
896
  # This optimization does not apply to issues from external sources.
897
  def cache_merge_request_closes_issues!(current_user = self.author)
898
    return unless project.issues_enabled?
899
    return if closed? || merged?
900

901
    transaction do
902
      self.merge_requests_closing_issues.delete_all
903

904
      closes_issues(current_user).each do |issue|
905 906
        next if issue.is_a?(ExternalIssue)

907
        self.merge_requests_closing_issues.create!(issue: issue)
908 909 910 911
      end
    end
  end

912 913 914 915 916 917 918 919 920 921 922 923
  def visible_closing_issues_for(current_user = self.author)
    strong_memoize(:visible_closing_issues_for) do
      if self.target_project.has_external_issue_tracker?
        closes_issues(current_user)
      else
        cached_closes_issues.select do |issue|
          Ability.allowed?(current_user, :read_issue, issue)
        end
      end
    end
  end

924
  # Return the set of issues that will be closed if this merge request is accepted.
925
  def closes_issues(current_user = self.author)
926
    if target_branch == project.default_branch
927
      messages = [title, description]
928
      messages.concat(commits.map(&:safe_message)) if merge_request_diff
929

930 931
      Gitlab::ClosingIssueExtractor.new(project, current_user)
        .closed_by_message(messages.join("\n"))
932 933 934 935 936
    else
      []
    end
  end

937
  def issues_mentioned_but_not_closing(current_user)
938
    return [] unless target_branch == project.default_branch
939

940
    ext = Gitlab::ReferenceExtractor.new(project, current_user)
941
    ext.analyze("#{title}\n#{description}")
942

943
    ext.issues - visible_closing_issues_for(current_user)
944 945
  end

946 947
  def target_project_path
    if target_project
948
      target_project.full_path
949 950 951 952 953 954 955
    else
      "(removed)"
    end
  end

  def source_project_path
    if source_project
956
      source_project.full_path
957 958 959 960 961
    else
      "(removed)"
    end
  end

962 963
  def source_project_namespace
    if source_project && source_project.namespace
964
      source_project.namespace.full_path
965 966 967 968 969
    else
      "(removed)"
    end
  end

970 971
  def target_project_namespace
    if target_project && target_project.namespace
972
      target_project.namespace.full_path
973 974 975 976 977
    else
      "(removed)"
    end
  end

978 979 980
  def source_branch_exists?
    return false unless self.source_project

981
    self.source_project.repository.branch_exists?(self.source_branch)
982 983 984 985 986
  end

  def target_branch_exists?
    return false unless self.target_project

987
    self.target_project.repository.branch_exists?(self.target_branch)
988 989
  end

990
  def default_merge_commit_message(include_description: false)
991
    closes_issues_references = visible_closing_issues_for.map do |issue|
992 993 994
      issue.to_reference(target_project)
    end

995 996 997 998
    message = [
      "Merge branch '#{source_branch}' into '#{target_branch}'",
      title
    ]
999

1000
    if !include_description && closes_issues_references.present?
1001
      message << "Closes #{closes_issues_references.to_sentence}"
1002
    end
1003

1004
    message << "#{description}" if include_description && description.present?
1005
    message << "See merge request #{to_reference(full: true)}"
1006

1007
    message.join("\n\n")
1008
  end
1009

1010 1011 1012 1013 1014 1015 1016
  # Returns the oldest multi-line commit message, or the MR title if none found
  def default_squash_commit_message
    strong_memoize(:default_squash_commit_message) do
      commits.without_merge_commits.reverse.find(&:description?)&.safe_message || title
    end
  end

J
James Lopez 已提交
1017 1018
  def reset_merge_when_pipeline_succeeds
    return unless merge_when_pipeline_succeeds?
1019

J
James Lopez 已提交
1020
    self.merge_when_pipeline_succeeds = false
Z
Zeger-Jan van de Weg 已提交
1021
    self.merge_user = nil
1022 1023 1024
    if merge_params
      merge_params.delete('should_remove_source_branch')
      merge_params.delete('commit_message')
1025
      merge_params.delete('squash_commit_message')
1026
    end
Z
Zeger-Jan van de Weg 已提交
1027 1028 1029 1030

    self.save
  end

1031
  # Return array of possible target branches
S
Steven Burgart 已提交
1032
  # depends on target project of MR
1033 1034 1035 1036 1037 1038 1039 1040 1041
  def target_branches
    if target_project.nil?
      []
    else
      target_project.repository.branch_names
    end
  end

  # Return array of possible source branches
S
Steven Burgart 已提交
1042
  # depends on source project of MR
1043 1044 1045 1046 1047 1048 1049
  def source_branches
    if source_project.nil?
      []
    else
      source_project.repository.branch_names
    end
  end
1050

1051
  def has_ci?
1052
    return false if has_no_commits?
1053

1054
    !!(head_pipeline_id || all_pipelines.any? || source_project&.ci_service)
1055 1056 1057 1058 1059
  end

  def branch_missing?
    !source_branch_exists? || !target_branch_exists?
  end
1060

1061
  def broken?
1062
    has_no_commits? || branch_missing? || cannot_be_merged?
1063 1064
  end

1065
  def can_be_merged_by?(user)
1066
    access = ::Gitlab::UserAccess.new(user, project: project)
1067
    access.can_update_branch?(target_branch)
1068 1069 1070 1071 1072
  end

  def can_be_merged_via_command_line_by?(user)
    access = ::Gitlab::UserAccess.new(user, project: project)
    access.can_push_to_branch?(target_branch)
1073 1074
  end

1075
  def mergeable_ci_state?
J
James Lopez 已提交
1076
    return true unless project.only_allow_merge_if_pipeline_succeeds?
1077
    return true unless head_pipeline
1078

1079
    actual_head_pipeline&.success? || actual_head_pipeline&.skipped?
1080 1081
  end

D
Douwe Maan 已提交
1082
  def environments_for(current_user)
1083
    return [] unless diff_head_commit
1084

D
Douwe Maan 已提交
1085 1086 1087
    @environments ||= Hash.new do |h, current_user|
      envs = EnvironmentsFinder.new(target_project, current_user,
        ref: target_branch, commit: diff_head_commit, with_tags: true).execute
1088

D
Douwe Maan 已提交
1089 1090 1091 1092
      if source_project
        envs.concat EnvironmentsFinder.new(source_project, current_user,
          ref: source_branch, commit: diff_head_commit).execute
      end
1093

D
Douwe Maan 已提交
1094
      h[current_user] = envs.uniq
1095
    end
D
Douwe Maan 已提交
1096 1097

    @environments[current_user]
1098 1099
  end

1100 1101 1102 1103 1104 1105 1106 1107 1108
  def state_human_name
    if merged?
      "Merged"
    elsif closed?
      "Closed"
    else
      "Open"
    end
  end
1109

1110 1111
  def state_icon_name
    if merged?
E
Eric Eastwood 已提交
1112
      "git-merge"
1113
    elsif closed?
E
Eric Eastwood 已提交
1114
      "close"
1115
    else
E
Eric Eastwood 已提交
1116
      "issue-open-m"
1117 1118 1119
    end
  end

1120 1121
  def fetch_ref!
    target_project.repository.fetch_source_branch!(source_project.repository, source_branch, ref_path)
1122 1123
  end

1124
  def ref_path
1125
    "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/head"
1126 1127
  end

1128 1129 1130 1131
  def merge_ref_path
    "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/merge"
  end

1132 1133 1134 1135 1136
  def in_locked_state
    begin
      lock_mr
      yield
    ensure
1137
      unlock_mr
1138 1139
    end
  end
1140

1141 1142 1143
  def diverged_commits_count
    cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits")

1144
    if cache.blank? || cache[:source_sha] != source_branch_sha || cache[:target_sha] != target_branch_sha
1145
      cache = {
1146 1147
        source_sha: source_branch_sha,
        target_sha: target_branch_sha,
1148 1149 1150 1151 1152 1153 1154 1155 1156
        diverged_commits_count: compute_diverged_commits_count
      }
      Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache)
    end

    cache[:diverged_commits_count]
  end

  def compute_diverged_commits_count
1157
    return 0 unless source_branch_sha && target_branch_sha
1158

L
Lin Jen-Shin 已提交
1159 1160
    target_project.repository
      .count_commits_between(source_branch_sha, target_branch_sha)
1161
  end
1162
  private :compute_diverged_commits_count
1163 1164 1165 1166 1167

  def diverged_from_target_branch?
    diverged_commits_count > 0
  end

1168
  def all_pipelines
1169
    return Ci::Pipeline.none unless source_project
1170

1171 1172 1173 1174 1175 1176 1177 1178 1179
    shas = all_commit_shas

    strong_memoize(:all_pipelines) do
      Ci::Pipeline.from_union(
        [source_project.ci_pipelines.merge_request_pipelines(self, shas),
         source_project.ci_pipelines.detached_merge_request_pipelines(self, shas),
         source_project.ci_pipelines.triggered_for_branch(source_branch).for_sha(shas)],
         remove_duplicates: false).sort_by_merge_request_pipelines
    end
1180 1181 1182
  end

  def update_head_pipeline
1183 1184 1185 1186
    find_actual_head_pipeline.try do |pipeline|
      self.head_pipeline = pipeline
      update_column(:head_pipeline_id, head_pipeline.id) if head_pipeline_id_changed?
    end
S
Shinya Maeda 已提交
1187 1188 1189 1190
  end

  def merge_request_pipeline_exists?
    merge_request_pipelines.exists?(sha: diff_head_sha)
1191
  end
1192

S
Shinya Maeda 已提交
1193 1194 1195 1196
  def has_test_reports?
    actual_head_pipeline&.has_test_reports?
  end

1197 1198 1199 1200
  def predefined_variables
    Gitlab::Ci::Variables::Collection.new.tap do |variables|
      variables.append(key: 'CI_MERGE_REQUEST_ID', value: id.to_s)
      variables.append(key: 'CI_MERGE_REQUEST_IID', value: iid.to_s)
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210
      variables.append(key: 'CI_MERGE_REQUEST_REF_PATH', value: ref_path.to_s)
      variables.append(key: 'CI_MERGE_REQUEST_PROJECT_ID', value: project.id.to_s)
      variables.append(key: 'CI_MERGE_REQUEST_PROJECT_PATH', value: project.full_path)
      variables.append(key: 'CI_MERGE_REQUEST_PROJECT_URL', value: project.web_url)
      variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME', value: target_branch.to_s)
      variables.append(key: 'CI_MERGE_REQUEST_TITLE', value: title)
      variables.append(key: 'CI_MERGE_REQUEST_ASSIGNEES', value: assignee.username) if assignee
      variables.append(key: 'CI_MERGE_REQUEST_MILESTONE', value: milestone.title) if milestone
      variables.append(key: 'CI_MERGE_REQUEST_LABELS', value: label_names.join(',')) if labels.present?
      variables.concat(source_project_variables)
1211 1212 1213
    end
  end

S
Shinya Maeda 已提交
1214
  def compare_test_reports
1215 1216
    unless has_test_reports?
      return { status: :error, status_reason: 'This merge request does not have test reports' }
S
Shinya Maeda 已提交
1217 1218
    end

G
Gilbert Roulot 已提交
1219 1220 1221 1222 1223 1224
    compare_reports(Ci::CompareTestReportsService)
  end

  def compare_reports(service_class)
    with_reactive_cache(service_class.name) do |data|
      unless service_class.new(project)
S
Shinya Maeda 已提交
1225 1226 1227 1228 1229 1230
        .latest?(base_pipeline, actual_head_pipeline, data)
        raise InvalidateReactiveCache
      end

      data
    end || { status: :parsing }
S
Shinya Maeda 已提交
1231 1232
  end

1233
  def calculate_reactive_cache(identifier, *args)
G
Gilbert Roulot 已提交
1234 1235 1236 1237 1238
    service_class = identifier.constantize

    raise NameError, service_class unless service_class < Ci::CompareReportsBaseService

    service_class.new(project).execute(base_pipeline, actual_head_pipeline)
S
Shinya Maeda 已提交
1239 1240
  end

1241
  def all_commits
1242
    # MySQL doesn't support LIMIT in a subquery.
M
micael.bergeron 已提交
1243 1244 1245 1246 1247
    diffs_relation = if Gitlab::Database.postgresql?
                       merge_request_diffs.recent
                     else
                       merge_request_diffs
                     end
1248

1249 1250 1251
    MergeRequestDiffCommit
      .where(merge_request_diff: diffs_relation)
      .limit(10_000)
1252 1253 1254 1255 1256 1257 1258
  end

  # Note that this could also return SHA from now dangling commits
  #
  def all_commit_shas
    @all_commit_shas ||= begin
      return commit_shas unless persisted?
M
micael.bergeron 已提交
1259

1260 1261
      all_commits.pluck(:sha).uniq
    end
1262 1263
  end

1264 1265 1266 1267
  def merge_commit
    @merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha
  end

1268 1269 1270 1271
  def short_merge_commit_sha
    Commit.truncate_sha(merge_commit_sha) if merge_commit_sha
  end

1272
  def can_be_reverted?(current_user)
1273
    return false unless merge_commit
1274
    return false unless merged_at
1275

1276 1277 1278 1279 1280
    # It is not guaranteed that Note#created_at will be strictly later than
    # MergeRequestMetric#merged_at. Nanoseconds on MySQL may break this
    # comparison, as will a HA environment if clocks are not *precisely*
    # synchronized. Add a minute's leeway to compensate for both possibilities
    cutoff = merged_at - 1.minute
1281

1282
    notes_association = notes_with_associations.where('created_at >= ?', cutoff)
1283 1284

    !merge_commit.has_been_reverted?(current_user, notes_association)
1285
  end
1286 1287 1288 1289 1290 1291 1292 1293 1294 1295

  def merged_at
    strong_memoize(:merged_at) do
      next unless merged?

      metrics&.merged_at ||
        merge_event&.created_at ||
        notes.system.reorder(nil).find_by(note: 'merged')&.created_at
    end
  end
1296 1297

  def can_be_cherry_picked?
F
Fatih Acet 已提交
1298
    merge_commit.present?
1299
  end
1300

1301
  def has_complete_diff_refs?
1302
    diff_refs && diff_refs.complete?
1303 1304
  end

1305
  # rubocop: disable CodeReuse/ServiceClass
1306
  def update_diff_discussion_positions(old_diff_refs:, new_diff_refs:, current_user: nil)
1307
    return unless has_complete_diff_refs?
1308 1309
    return if new_diff_refs == old_diff_refs

1310 1311
    active_diff_discussions = self.notes.new_diff_notes.discussions.select do |discussion|
      discussion.active?(old_diff_refs)
1312
    end
1313
    return if active_diff_discussions.empty?
1314

1315
    paths = active_diff_discussions.flat_map { |n| n.diff_file.paths }.uniq
1316

1317
    service = Discussions::UpdateDiffPositionService.new(
1318
      self.project,
1319
      current_user,
1320 1321 1322 1323 1324
      old_diff_refs: old_diff_refs,
      new_diff_refs: new_diff_refs,
      paths: paths
    )

1325 1326
    active_diff_discussions.each do |discussion|
      service.execute(discussion)
1327
    end
1328 1329 1330 1331 1332 1333

    if project.resolve_outdated_diff_discussions?
      MergeRequests::ResolvedDiscussionNotificationService
        .new(project, current_user)
        .execute(self)
    end
1334
  end
1335
  # rubocop: enable CodeReuse/ServiceClass
1336

1337 1338 1339
  def keep_around_commit
    project.repository.keep_around(self.merge_commit_sha)
  end
1340

1341
  def has_commits?
1342
    merge_request_diff && commits_count > 0
1343 1344 1345 1346 1347
  end

  def has_no_commits?
    !has_commits?
  end
1348

1349
  def mergeable_with_quick_action?(current_user, autocomplete_precheck: false, last_diff_sha: nil)
1350 1351 1352 1353 1354
    return false unless can_be_merged_by?(current_user)

    return true if autocomplete_precheck

    return false unless mergeable?(skip_ci_check: true)
1355
    return false if actual_head_pipeline && !(actual_head_pipeline.success? || actual_head_pipeline.active?)
1356 1357 1358 1359
    return false if last_diff_sha != diff_head_sha

    true
  end
1360

S
Shinya Maeda 已提交
1361
  def base_pipeline
1362
    @base_pipeline ||= project.ci_pipelines
S
Shinya Maeda 已提交
1363
      .order(id: :desc)
1364
      .find_by(sha: diff_base_sha, ref: target_branch)
S
Shinya Maeda 已提交
1365 1366
  end

F
Felipe Artur 已提交
1367 1368 1369 1370
  def discussions_rendered_on_frontend?
    true
  end

1371
  # rubocop: disable CodeReuse/ServiceClass
1372 1373 1374
  def update_project_counter_caches
    Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
  end
1375
  # rubocop: enable CodeReuse/ServiceClass
1376

M
micael.bergeron 已提交
1377
  def first_contribution?
M
micael.bergeron 已提交
1378
    return false if project.team.max_member_access(author_id) > Gitlab::Access::GUEST
M
micael.bergeron 已提交
1379

M
micael.bergeron 已提交
1380 1381
    project.merge_requests.merged.where(author_id: author_id).empty?
  end
1382

1383 1384 1385
  # TODO: remove once production database rename completes
  alias_attribute :allow_collaboration, :allow_maintainer_to_push

1386
  def allow_collaboration
1387
    collaborative_push_possible? && allow_maintainer_to_push
1388 1389
  end

1390
  alias_method :allow_collaboration?, :allow_collaboration
1391

1392
  def collaborative_push_possible?
1393 1394 1395 1396 1397 1398
    source_project.present? && for_fork? &&
      target_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE &&
      source_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE &&
      !ProtectedBranch.protected?(source_project, source_branch)
  end

1399 1400
  def can_allow_collaboration?(user)
    collaborative_push_possible? &&
1401 1402
      Ability.allowed?(user, :push_code, source_project)
  end
1403 1404 1405 1406 1407 1408 1409

  def squash_in_progress?
    # The source project can be deleted
    return false unless source_project

    source_project.repository.squash_in_progress?(id)
  end
1410 1411 1412 1413

  private

  def find_actual_head_pipeline
1414
    all_pipelines.for_sha_or_source_sha(diff_head_sha).first
1415
  end
1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426

  def source_project_variables
    Gitlab::Ci::Variables::Collection.new.tap do |variables|
      break variables unless source_project

      variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID', value: source_project.id.to_s)
      variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH', value: source_project.full_path)
      variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL', value: source_project.web_url)
      variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', value: source_branch.to_s)
    end
  end
D
Dmitriy Zaporozhets 已提交
1427
end