summaryrefslogtreecommitdiff
path: root/app/models/merge_request.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/merge_request.rb')
-rw-r--r--app/models/merge_request.rb115
1 files changed, 92 insertions, 23 deletions
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 82d0ae90d77..422f138c4ea 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -5,6 +5,9 @@ class MergeRequest < ActiveRecord::Base
include Referable
include IgnorableColumn
include TimeTrackable
+ include ManualInverseAssociation
+ include EachBatch
+ include ThrottledTouch
ignore_column :locked_at,
:ref_fetched
@@ -14,9 +17,28 @@ class MergeRequest < ActiveRecord::Base
belongs_to :merge_user, class_name: "User"
has_many :merge_request_diffs
+
has_one :merge_request_diff,
-> { order('merge_request_diffs.id DESC') }, inverse_of: :merge_request
+ 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.
+ def merge_request_diff(*args)
+ fallback = latest_merge_request_diff if args.empty? && !association(:merge_request_diff).loaded?
+
+ fallback || super
+ end
+
belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline"
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -31,7 +53,6 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed
- after_commit :update_project_counter_caches, on: :destroy
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
@@ -125,6 +146,13 @@ class MergeRequest < ActiveRecord::Base
'!'
end
+ # 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
+ def actual_head_pipeline
+ head_pipeline&.sha == diff_head_sha ? head_pipeline : nil
+ end
+
# Pattern used to extract `!123` merge request references from text
#
# This pattern supports cross-project references.
@@ -167,6 +195,22 @@ class MergeRequest < ActiveRecord::Base
where("merge_requests.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
end
+ # 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
+
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
def self.work_in_progress?(title)
@@ -181,6 +225,12 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
+ # 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
+
def hook_attrs
Gitlab::HookData::MergeRequestBuilder.new(self).build
end
@@ -241,9 +291,9 @@ class MergeRequest < ActiveRecord::Base
if persisted?
merge_request_diff.commit_shas
elsif compare_commits
- compare_commits.reverse.map(&:sha)
+ compare_commits.to_a.reverse.map(&:sha)
else
- []
+ Array(diff_head_sha)
end
end
@@ -322,16 +372,28 @@ class MergeRequest < ActiveRecord::Base
# We use these attributes to force these to the intended values.
attr_writer :target_branch_sha, :source_branch_sha
+ 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
+
def source_branch_head
return unless source_project
- source_branch_ref = @source_branch_sha || source_branch
source_project.repository.commit(source_branch_ref) if source_branch_ref
end
def target_branch_head
- target_branch_ref = @target_branch_sha || target_branch
- target_project.repository.commit(target_branch_ref) if target_branch_ref
+ target_project.repository.commit(target_branch_ref)
end
def branch_merge_base_commit
@@ -440,7 +502,7 @@ class MergeRequest < ActiveRecord::Base
def merge_request_diff_for(diff_refs_or_sha)
@merge_request_diffs_by_diff_refs_or_sha ||= Hash.new do |h, diff_refs_or_sha|
- diffs = merge_request_diffs.viewable.select_without_diff
+ diffs = merge_request_diffs.viewable
h[diff_refs_or_sha] =
if diff_refs_or_sha.is_a?(Gitlab::Diff::DiffRefs)
diffs.find_by_diff_refs(diff_refs_or_sha)
@@ -576,7 +638,7 @@ class MergeRequest < ActiveRecord::Base
commit_notes = Note
.except(:order)
.where(project_id: [source_project_id, target_project_id])
- .where(noteable_type: 'Commit', commit_id: commit_ids)
+ .for_commit_id(commit_ids)
# 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
@@ -587,6 +649,7 @@ class MergeRequest < ActiveRecord::Base
.to_sql
Note.from("(#{union}) #{Note.table_name}")
+ .includes(:noteable)
end
alias_method :discussion_notes, :related_notes
@@ -768,8 +831,9 @@ class MergeRequest < ActiveRecord::Base
def mergeable_ci_state?
return true unless project.only_allow_merge_if_pipeline_succeeds?
+ return true unless head_pipeline
- !head_pipeline || head_pipeline.success? || head_pipeline.skipped?
+ actual_head_pipeline&.success? || actual_head_pipeline&.skipped?
end
def environments_for(current_user)
@@ -845,7 +909,8 @@ class MergeRequest < ActiveRecord::Base
def compute_diverged_commits_count
return 0 unless source_branch_sha && target_branch_sha
- Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_branch_sha, target_branch_sha).size
+ target_project.repository
+ .count_commits_between(source_branch_sha, target_branch_sha)
end
private :compute_diverged_commits_count
@@ -861,18 +926,26 @@ class MergeRequest < ActiveRecord::Base
.order(id: :desc)
end
+ def all_commits
+ # MySQL doesn't support LIMIT in a subquery.
+ diffs_relation = if Gitlab::Database.postgresql?
+ merge_request_diffs.recent
+ else
+ merge_request_diffs
+ end
+
+ MergeRequestDiffCommit
+ .where(merge_request_diff: diffs_relation)
+ .limit(10_000)
+ end
+
# Note that this could also return SHA from now dangling commits
#
def all_commit_shas
- if persisted?
- column_shas = MergeRequestDiffCommit.where(merge_request_diff: merge_request_diffs).limit(10_000).pluck('sha')
- serialised_shas = merge_request_diffs.where.not(st_commits: nil).flat_map(&:commit_shas)
+ @all_commit_shas ||= begin
+ return commit_shas unless persisted?
- (column_shas + serialised_shas).uniq
- elsif compare_commits
- compare_commits.to_a.reverse.map(&:id)
- else
- [diff_head_sha]
+ all_commits.pluck(:sha).uniq
end
end
@@ -940,16 +1013,12 @@ class MergeRequest < ActiveRecord::Base
return true if autocomplete_precheck
return false unless mergeable?(skip_ci_check: true)
- return false if head_pipeline && !(head_pipeline.success? || head_pipeline.active?)
+ return false if actual_head_pipeline && !(actual_head_pipeline.success? || actual_head_pipeline.active?)
return false if last_diff_sha != diff_head_sha
true
end
- def update_project_counter_caches?
- state_changed?
- end
-
def update_project_counter_caches
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end