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.rb234
1 files changed, 186 insertions, 48 deletions
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index b1fb3ce5d69..aec555dcec0 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -10,14 +10,18 @@ class MergeRequest < ActiveRecord::Base
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
belongs_to :merge_user, class_name: "User"
- has_one :merge_request_diff, dependent: :destroy
+ has_many :merge_request_diffs, dependent: :destroy
+ has_one :merge_request_diff,
+ -> { order('merge_request_diffs.id DESC') }
has_many :events, as: :target, dependent: :destroy
+ has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
+
serialize :merge_params, Hash
- after_create :create_merge_request_diff, unless: :importing?
- after_update :update_merge_request_diff
+ after_create :ensure_merge_request_diff, unless: :importing?
+ after_update :reload_diff_if_branch_changed
delegate :commits, :real_size, to: :merge_request_diff, prefix: nil
@@ -89,13 +93,13 @@ class MergeRequest < ActiveRecord::Base
end
end
- validates :source_project, presence: true, unless: [:allow_broken, :importing?]
+ validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?
- validate :validate_branches, unless: [:allow_broken, :importing?]
- validate :validate_fork
+ validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?]
+ validate :validate_fork, unless: :closed_without_fork?
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
@@ -104,6 +108,7 @@ class MergeRequest < ActiveRecord::Base
scope :from_project, ->(project) { where(source_project_id: project.id) }
scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
+ scope :from_source_branches, ->(branches) { where(source_branch: branches) }
scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) }
@@ -169,10 +174,10 @@ class MergeRequest < ActiveRecord::Base
end
def diffs(diff_options = nil)
- if self.compare
- self.compare.diffs(diff_options)
+ if compare
+ compare.diffs(diff_options)
else
- Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options)
+ merge_request_diff.diffs(diff_options)
end
end
@@ -183,8 +188,8 @@ class MergeRequest < ActiveRecord::Base
def diff_base_commit
if persisted?
merge_request_diff.base_commit
- elsif diff_start_commit && diff_head_commit
- self.target_project.merge_base_commit(diff_start_sha, diff_head_sha)
+ else
+ branch_merge_base_commit
end
end
@@ -237,12 +242,21 @@ class MergeRequest < ActiveRecord::Base
def source_branch_head
source_branch_ref = @source_branch_sha || source_branch
- source_project.repository.commit(source_branch) if source_branch_ref
+ 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) if target_branch_ref
+ target_project.repository.commit(target_branch_ref) if target_branch_ref
+ end
+
+ 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
def target_branch_sha
@@ -266,16 +280,16 @@ class MergeRequest < ActiveRecord::Base
# Return diff_refs instance trying to not touch the git repository
def diff_sha_refs
if merge_request_diff && merge_request_diff.diff_refs_by_sha?
- return Gitlab::Diff::DiffRefs.new(
- base_sha: merge_request_diff.base_commit_sha,
- start_sha: merge_request_diff.start_commit_sha,
- head_sha: merge_request_diff.head_commit_sha
- )
+ merge_request_diff.diff_refs
else
diff_refs
end
end
+ def branch_merge_base_sha
+ branch_merge_base_commit.try(:sha)
+ end
+
def validate_branches
if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target"
@@ -293,36 +307,59 @@ class MergeRequest < ActiveRecord::Base
def validate_fork
return true unless target_project && source_project
+ return true if target_project == source_project
+ return true unless forked_source_project_missing?
- if target_project == source_project
- true
- else
- # If source and target projects are different
- # we should check if source project is actually a fork of target project
- if source_project.forked_from?(target_project)
- true
- else
- errors.add :validate_fork,
- 'Source project is not a fork of target project'
- end
- end
+ errors.add :validate_fork,
+ 'Source project is not a fork of the target project'
+ end
+
+ def closed_without_fork?
+ closed? && forked_source_project_missing?
+ end
+
+ def closed_without_source_project?
+ closed? && !source_project
+ end
+
+ def forked_source_project_missing?
+ return false unless for_fork?
+ return true unless source_project
+
+ !source_project.forked_from?(target_project)
end
- def update_merge_request_diff
+ def reopenable?
+ return false if closed_without_fork? || closed_without_source_project? || merged?
+
+ closed?
+ end
+
+ def ensure_merge_request_diff
+ merge_request_diff || create_merge_request_diff
+ end
+
+ def create_merge_request_diff
+ merge_request_diffs.create
+ reload_merge_request_diff
+ end
+
+ def reload_merge_request_diff
+ merge_request_diff(true)
+ end
+
+ def reload_diff_if_branch_changed
if source_branch_changed? || target_branch_changed?
reload_diff
end
end
def reload_diff
- return unless merge_request_diff && open?
+ return unless open?
old_diff_refs = self.diff_refs
-
- merge_request_diff.reload_content
-
+ create_merge_request_diff
MergeRequests::MergeRequestDiffCacheService.new.execute(self)
-
new_diff_refs = self.diff_refs
update_diff_notes_positions(
@@ -386,7 +423,7 @@ class MergeRequest < ActiveRecord::Base
def can_remove_source_branch?(current_user)
!source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) &&
- Ability.abilities.allowed?(current_user, :push_code, source_project) &&
+ Ability.allowed?(current_user, :push_code, source_project) &&
diff_head_commit == source_branch_head
end
@@ -417,6 +454,32 @@ class MergeRequest < ActiveRecord::Base
)
end
+ def discussions
+ @discussions ||= self.mr_and_commit_notes.
+ inc_relations_for_view.
+ fresh.
+ discussions
+ end
+
+ def diff_discussions
+ @diff_discussions ||= self.notes.diff_notes.discussions
+ end
+
+ def find_diff_discussion(discussion_id)
+ notes = self.notes.diff_notes.where(discussion_id: discussion_id).fresh.to_a
+ return if notes.empty?
+
+ Discussion.new(notes)
+ end
+
+ def discussions_resolvable?
+ diff_discussions.any?(&:resolvable?)
+ end
+
+ def discussions_resolved?
+ discussions_resolvable? && diff_discussions.none?(&:to_be_resolved?)
+ end
+
def hook_attrs
attrs = {
source: source_project.try(:hook_attrs),
@@ -440,6 +503,19 @@ class MergeRequest < ActiveRecord::Base
target_project
end
+ # 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.
+ def cache_merge_request_closes_issues!(current_user = self.author)
+ transaction do
+ self.merge_requests_closing_issues.delete_all
+ closes_issues(current_user).each do |issue|
+ self.merge_requests_closing_issues.create!(issue: issue)
+ end
+ end
+ end
+
def closes_issue?(issue)
closes_issues.include?(issue)
end
@@ -447,7 +523,8 @@ class MergeRequest < ActiveRecord::Base
# Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author)
if target_branch == project.default_branch
- messages = commits.map(&:safe_message) << description
+ messages = [description]
+ messages.concat(commits.map(&:safe_message)) if merge_request_diff
Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(messages.join("\n"))
@@ -513,13 +590,11 @@ class MergeRequest < ActiveRecord::Base
end
def merge_commit_message
- message = "Merge branch '#{source_branch}' into '#{target_branch}'"
- message << "\n\n"
- message << title.to_s
- message << "\n\n"
- message << description.to_s
- message << "\n\n"
- message << "See merge request !#{iid}"
+ message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n"
+ message << "#{title}\n\n"
+ message << "#{description}\n\n" if description.present?
+ message << "See merge request #{to_reference}"
+
message
end
@@ -590,6 +665,17 @@ class MergeRequest < ActiveRecord::Base
!pipeline || pipeline.success?
end
+ def environments
+ return [] unless diff_head_commit
+
+ environments = source_project.environments_for(
+ source_branch, diff_head_commit)
+ environments += target_project.environments_for(
+ target_branch, diff_head_commit, with_tags: true)
+
+ environments.uniq
+ end
+
def state_human_name
if merged?
"Merged"
@@ -665,8 +751,34 @@ class MergeRequest < ActiveRecord::Base
diverged_commits_count > 0
end
+ def commits_sha
+ commits.map(&:sha)
+ end
+
def pipeline
- @pipeline ||= source_project.pipeline(diff_head_sha, source_branch) if diff_head_sha && source_project
+ return unless diff_head_sha && source_project
+
+ @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
+ end
+
+ def all_pipelines
+ return unless source_project
+
+ @all_pipelines ||= begin
+ sha = if persisted?
+ all_commits_sha
+ else
+ diff_head_sha
+ end
+
+ source_project.pipelines.order(id: :desc).
+ where(sha: sha, ref: source_branch)
+ end
+ end
+
+ # Note that this could also return SHA from now dangling commits
+ def all_commits_sha
+ merge_request_diffs.flat_map(&:commits_sha).uniq
end
def merge_commit
@@ -681,12 +793,12 @@ class MergeRequest < ActiveRecord::Base
merge_commit
end
- def support_new_diff_notes?
+ def has_complete_diff_refs?
diff_sha_refs && diff_sha_refs.complete?
end
def update_diff_notes_positions(old_diff_refs:, new_diff_refs:)
- return unless support_new_diff_notes?
+ return unless has_complete_diff_refs?
return if new_diff_refs == old_diff_refs
active_diff_notes = self.notes.diff_notes.select do |note|
@@ -714,4 +826,30 @@ class MergeRequest < ActiveRecord::Base
def keep_around_commit
project.repository.keep_around(self.merge_commit_sha)
end
+
+ def conflicts
+ @conflicts ||= Gitlab::Conflict::FileCollection.new(self)
+ end
+
+ def conflicts_can_be_resolved_by?(user)
+ access = ::Gitlab::UserAccess.new(user, project: source_project)
+ access.can_push_to_branch?(source_branch)
+ end
+
+ def conflicts_can_be_resolved_in_ui?
+ return @conflicts_can_be_resolved_in_ui if defined?(@conflicts_can_be_resolved_in_ui)
+
+ return @conflicts_can_be_resolved_in_ui = false unless cannot_be_merged?
+ return @conflicts_can_be_resolved_in_ui = false unless has_complete_diff_refs?
+
+ begin
+ # Try to parse each conflict. If the MR's mergeable status hasn't been updated,
+ # ensure that we don't say there are conflicts to resolve when there are no conflict
+ # files.
+ conflicts.files.each(&:lines)
+ @conflicts_can_be_resolved_in_ui = conflicts.files.length > 0
+ rescue Rugged::OdbError, Gitlab::Conflict::Parser::ParserError, Gitlab::Conflict::FileCollection::ConflictSideMissing
+ @conflicts_can_be_resolved_in_ui = false
+ end
+ end
end