merge_request.rb 7.7 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 35
  has_one :merge_request_diff, dependent: :destroy

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

37
  attr_accessible :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :author_id_of_changes, :state_event, :description
38

A
Andrey Kumanyaev 已提交
39 40
  attr_accessor :should_remove_source_branch

D
Dmitriy Zaporozhets 已提交
41 42 43 44
  # 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 已提交
45
  state_machine :state, initial: :opened do
A
Andrew8xx8 已提交
46 47 48 49 50 51 52 53 54
    event :close do
      transition [:reopened, :opened] => :closed
    end

    event :merge do
      transition [:reopened, :opened] => :merged
    end

    event :reopen do
A
Andrew8xx8 已提交
55
      transition closed: :reopened
A
Andrew8xx8 已提交
56 57 58 59 60 61 62 63
    end

    state :opened
    state :reopened
    state :closed
    state :merged
  end

64 65 66 67 68 69 70 71 72 73 74 75 76
  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

77
    state :unchecked
78 79 80
    state :can_be_merged
    state :cannot_be_merged
  end
81

D
Dmitriy Zaporozhets 已提交
82
  validates :source_project, presence: true, unless: :allow_broken
A
Andrey Kumanyaev 已提交
83
  validates :source_branch, presence: true
I
Izaak Alpert 已提交
84
  validates :target_project, presence: true
A
Andrey Kumanyaev 已提交
85
  validates :target_branch, presence: true
I
Izaak Alpert 已提交
86
  validate :validate_branches
D
Dmitriy Zaporozhets 已提交
87

88 89
  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 已提交
90 91
  scope :opened, -> { with_state(:opened) }
  scope :closed, -> { with_state(:closed) }
A
Andrew8xx8 已提交
92
  scope :merged, -> { with_state(:merged) }
I
Izaak Alpert 已提交
93
  scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
A
Andrew8xx8 已提交
94
  scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
95
  scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
I
Izaak Alpert 已提交
96
  scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
97
  scope :of_projects, ->(ids) { where(target_project_id: ids) }
98 99 100 101
  # Closed scope for merge request should return
  # both merged and closed mr's
  scope :closed, -> { with_states(:closed, :merged) }

102
  def validate_branches
103
    if target_project == source_project && target_branch == source_branch
I
Izaak Alpert 已提交
104
      errors.add :branch_conflict, "You can not use same project/branch for source and target"
105
    end
106

107
    if opened? || reopened?
I
Izaak Alpert 已提交
108
      similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
109
      similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
110

111
      if similar_mrs.any?
I
Izaak Alpert 已提交
112
        errors.add :base, "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
113
      end
114
    end
115 116
  end

117
  def reload_code
118
    merge_request_diff.reload_content if opened?
119 120
  end

121
  def check_if_can_be_merged
122 123 124 125 126
    if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
      mark_as_mergeable
    else
      mark_as_unmergeable
    end
127 128
  end

D
Dmitriy Zaporozhets 已提交
129
  def merge_event
I
Izaak Alpert 已提交
130
    self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
D
Dmitriy Zaporozhets 已提交
131 132
  end

133
  def closed_event
I
Izaak Alpert 已提交
134
    self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
135 136 137
  end

  def merge!(user_id)
138
    self.author_id_of_changes = user_id
A
Andrew8xx8 已提交
139
    self.merge
140
  end
141

D
Dmitriy Zaporozhets 已提交
142 143
  def automerge!(current_user, commit_message = nil)
    if Gitlab::Satellite::MergeAction.new(current_user, self).merge!(commit_message) && self.unmerged_commits.empty?
144 145 146
      self.merge!(current_user.id)
      true
    end
Z
Zevs 已提交
147
  rescue
148
    mark_as_unmergeable
149 150
    false
  end
R
randx 已提交
151

152 153
  def mr_and_commit_notes
    commit_ids = commits.map(&:id)
154 155 156 157 158
    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
    )
159
  end
160

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

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

I
Izaak Alpert 已提交
175 176 177 178 179 180 181 182
  def for_fork?
    target_project != source_project
  end

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

183 184 185 186
  def project
    target_project
  end

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

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

201 202 203 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
  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 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242
  # 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

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