merge_request.rb 7.8 KB
Newer Older
D
Dmitriy Zaporozhets 已提交
1 2 3 4
# == Schema Information
#
# Table name: merge_requests
#
D
Dmitriy Zaporozhets 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#  id                :integer          not null, primary key
#  target_branch     :string(255)      not null
#  source_branch     :string(255)      not null
#  source_project_id :integer          not null
#  author_id         :integer
#  assignee_id       :integer
#  title             :string(255)
#  created_at        :datetime         not null
#  updated_at        :datetime         not null
#  st_commits        :text(2147483647)
#  st_diffs          :text(2147483647)
#  milestone_id      :integer
#  state             :string(255)
#  merge_status      :string(255)
#  target_project_id :integer          not null
#  iid               :integer
D
Dmitriy Zaporozhets 已提交
21
#  description       :text
D
Dmitriy Zaporozhets 已提交
22
#
D
Dmitriy Zaporozhets 已提交
23

24
require Rails.root.join("app/models/commit")
25
require Rails.root.join("lib/static_model")
26

D
Dmitriy Zaporozhets 已提交
27
class MergeRequest < ActiveRecord::Base
28
  include Issuable
29
  include InternalId
30

31 32
  belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
  belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
33

34
  has_one :merge_request_diff, dependent: :destroy
35
  after_create :create_merge_request_diff
36 37

  delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil
I
Izaak Alpert 已提交
38

39
  attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event, :description
40

A
Andrey Kumanyaev 已提交
41 42
  attr_accessor :should_remove_source_branch

D
Dmitriy Zaporozhets 已提交
43 44 45 46
  # When this attribute is true some MR validation is ignored
  # It allows us to close or modify broken merge requests
  attr_accessor :allow_broken

A
Andrew8xx8 已提交
47
  state_machine :state, initial: :opened do
A
Andrew8xx8 已提交
48 49 50 51 52
    event :close do
      transition [:reopened, :opened] => :closed
    end

    event :merge do
D
Dmitriy Zaporozhets 已提交
53
      transition [:reopened, :opened, :locked] => :merged
A
Andrew8xx8 已提交
54 55 56
    end

    event :reopen do
A
Andrew8xx8 已提交
57
      transition closed: :reopened
A
Andrew8xx8 已提交
58 59
    end

D
Dmitriy Zaporozhets 已提交
60 61 62 63 64 65 66 67
    event :lock do
      transition [:reopened, :opened] => :locked
    end

    event :unlock do
      transition locked: :reopened
    end

A
Andrew8xx8 已提交
68 69 70 71
    state :opened
    state :reopened
    state :closed
    state :merged
D
Dmitriy Zaporozhets 已提交
72
    state :locked
A
Andrew8xx8 已提交
73 74
  end

75 76 77 78 79 80 81 82 83 84 85 86 87
  state_machine :merge_status, initial: :unchecked do
    event :mark_as_unchecked do
      transition [:can_be_merged, :cannot_be_merged] => :unchecked
    end

    event :mark_as_mergeable do
      transition unchecked: :can_be_merged
    end

    event :mark_as_unmergeable do
      transition unchecked: :cannot_be_merged
    end

88
    state :unchecked
89 90 91
    state :can_be_merged
    state :cannot_be_merged
  end
92

D
Dmitriy Zaporozhets 已提交
93
  validates :source_project, presence: true, unless: :allow_broken
A
Andrey Kumanyaev 已提交
94
  validates :source_branch, presence: true
I
Izaak Alpert 已提交
95
  validates :target_project, presence: true
A
Andrey Kumanyaev 已提交
96
  validates :target_branch, presence: true
I
Izaak Alpert 已提交
97
  validate :validate_branches
D
Dmitriy Zaporozhets 已提交
98

99 100
  scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
  scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
I
Izaak Alpert 已提交
101 102
  scope :opened, -> { with_state(:opened) }
  scope :closed, -> { with_state(:closed) }
A
Andrew8xx8 已提交
103
  scope :merged, -> { with_state(:merged) }
I
Izaak Alpert 已提交
104
  scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
A
Andrew8xx8 已提交
105
  scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
106
  scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
I
Izaak Alpert 已提交
107
  scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
108
  scope :of_projects, ->(ids) { where(target_project_id: ids) }
109 110 111 112
  # Closed scope for merge request should return
  # both merged and closed mr's
  scope :closed, -> { with_states(:closed, :merged) }

113
  def validate_branches
114
    if target_project == source_project && target_branch == source_branch
I
Izaak Alpert 已提交
115
      errors.add :branch_conflict, "You can not use same project/branch for source and target"
116
    end
117

118
    if opened? || reopened?
I
Izaak Alpert 已提交
119
      similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
120
      similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
121

122
      if similar_mrs.any?
I
Izaak Alpert 已提交
123
        errors.add :base, "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
124
      end
125
    end
126 127
  end

128
  def reload_code
129
    merge_request_diff.reload_content if opened?
130 131
  end

132
  def check_if_can_be_merged
133 134 135 136 137
    if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
      mark_as_mergeable
    else
      mark_as_unmergeable
    end
138 139
  end

D
Dmitriy Zaporozhets 已提交
140
  def merge_event
I
Izaak Alpert 已提交
141
    self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
D
Dmitriy Zaporozhets 已提交
142 143
  end

144
  def closed_event
I
Izaak Alpert 已提交
145
    self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
146 147
  end

D
Dmitriy Zaporozhets 已提交
148
  def automerge!(current_user, commit_message = nil)
D
Dmitriy Zaporozhets 已提交
149
    MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message)
150
  end
R
randx 已提交
151

152
  def mr_and_commit_notes
153 154 155 156
    # Fetch comments only from last 100 commits
    commits_for_notes_limit = 100
    commit_ids = commits.last(commits_for_notes_limit).map(&:id)

157 158 159 160 161
    project.notes.where(
      "(noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR (noteable_type = 'Commit' AND commit_id IN (:commit_ids))",
      mr_id: id,
      commit_ids: commit_ids
    )
162
  end
163

164 165 166
  # Returns the raw diff for this merge request
  #
  # see "git diff"
I
Izaak Alpert 已提交
167 168
  def to_diff(current_user)
    Gitlab::Satellite::MergeAction.new(current_user, self).diff_in_satellite
169 170 171 172 173
  end

  # Returns the commit as a series of email patches.
  #
  # see "git format-patch"
I
Izaak Alpert 已提交
174 175
  def to_patch(current_user)
    Gitlab::Satellite::MergeAction.new(current_user, self).format_patch
176
  end
177

I
Izaak Alpert 已提交
178 179 180 181 182 183 184 185
  def for_fork?
    target_project != source_project
  end

  def disallow_source_branch_removal?
    (source_project.root_ref? source_branch) || for_fork?
  end

186 187 188 189
  def project
    target_project
  end

190 191 192
  # Return the set of issues that will be closed if this merge request is accepted.
  def closes_issues
    if target_branch == project.default_branch
193
      commits.map { |c| c.closes_issues(project) }.flatten.uniq.sort_by(&:id)
194 195 196 197 198 199 200 201 202 203
    else
      []
    end
  end

  # Mentionable override.
  def gfm_reference
    "merge request !#{iid}"
  end

204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
  def target_project_path
    if target_project
      target_project.path_with_namespace
    else
      "(removed)"
    end
  end

  def source_project_path
    if source_project
      source_project.path_with_namespace
    else
      "(removed)"
    end
  end

  def source_branch_exists?
    return false unless self.source_project

    self.source_project.repository.branch_names.include?(self.source_branch)
  end

  def target_branch_exists?
    return false unless self.target_project

    self.target_project.repository.branch_names.include?(self.target_branch)
  end

D
Drew Blessing 已提交
232 233 234 235 236 237 238 239 240 241 242 243 244 245
  # Reset merge request events cache
  #
  # Since we do cache @event we need to reset cache in special cases:
  # * when a merge request is updated
  # Events cache stored like  events/23-20130109142513.
  # The cache key includes updated_at timestamp.
  # Thus it will automatically generate a new fragment
  # when the event is updated because the key changes.
  def reset_events_cache
    Event.where(target_id: self.id, target_type: 'MergeRequest').
        order('id DESC').limit(100).
        update_all(updated_at: Time.now)
  end

246 247 248
  def merge_commit_message
    message = "Merge branch '#{source_branch}' into '#{target_branch}'"
    message << "\n\n"
D
Dmitriy Zaporozhets 已提交
249
    message << title.to_s
250
    message << "\n\n"
D
Dmitriy Zaporozhets 已提交
251 252
    message << description.to_s
    message
253
  end
D
Dmitriy Zaporozhets 已提交
254
end