2012-11-19 13:24:05 -05:00
# == Schema Information
#
# Table name: merge_requests
#
2013-08-21 05:34:02 -04:00
# 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)
2014-04-09 08:05:03 -04:00
# created_at :datetime
# updated_at :datetime
2013-08-21 05:34:02 -04:00
# milestone_id :integer
# state :string(255)
# merge_status :string(255)
# target_project_id :integer not null
# iid :integer
2013-10-01 08:15:28 -04:00
# description :text
2014-08-25 05:25:02 -04:00
# position :integer default(0)
2015-01-22 12:40:03 -05:00
# locked_at :datetime
2013-03-15 09:16:02 -04:00
#
2012-11-19 13:24:05 -05:00
2012-09-26 14:52:01 -04:00
require Rails . root . join ( " app/models/commit " )
2013-01-02 17:01:08 -05:00
require Rails . root . join ( " lib/static_model " )
2012-03-14 18:57:43 -04:00
2011-11-28 02:39:43 -05:00
class MergeRequest < ActiveRecord :: Base
2013-01-03 02:06:07 -05:00
include Issuable
2014-10-05 01:53:44 -04:00
include Taskable
2013-08-21 05:16:26 -04:00
include InternalId
2015-02-05 19:49:41 -05:00
include Sortable
2012-06-07 08:44:57 -04:00
2013-07-16 17:14:03 -04:00
belongs_to :target_project , foreign_key : :target_project_id , class_name : " Project "
belongs_to :source_project , foreign_key : :source_project_id , class_name : " Project "
2014-01-22 13:22:20 -05:00
2014-01-22 08:20:20 -05:00
has_one :merge_request_diff , dependent : :destroy
2014-02-18 13:17:26 -05:00
2014-01-22 13:22:20 -05:00
after_create :create_merge_request_diff
2014-02-18 13:17:26 -05:00
after_update :update_merge_request_diff
2014-01-22 08:20:20 -05:00
delegate :commits , :diffs , :last_commit , :last_commit_short_sha , to : :merge_request_diff , prefix : nil
2013-04-25 10:15:33 -04:00
2012-10-08 20:10:04 -04:00
attr_accessor :should_remove_source_branch
2013-12-12 05:20:54 -05:00
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
attr_accessor :allow_broken
2014-07-15 08:34:06 -04:00
# Temporary fields to store compare vars
# when creating new merge request
2014-07-28 13:54:40 -04:00
attr_accessor :can_be_created , :compare_failed ,
2014-07-15 11:28:21 -04:00
:compare_commits , :compare_diffs
2014-07-15 08:34:06 -04:00
2013-02-18 08:22:18 -05:00
state_machine :state , initial : :opened do
2013-02-18 03:40:56 -05:00
event :close do
transition [ :reopened , :opened ] = > :closed
end
event :merge do
2014-01-24 07:18:32 -05:00
transition [ :reopened , :opened , :locked ] = > :merged
2013-02-18 03:40:56 -05:00
end
event :reopen do
2013-02-18 08:22:18 -05:00
transition closed : :reopened
2013-02-18 03:40:56 -05:00
end
2014-06-26 08:09:17 -04:00
event :lock_mr do
2014-01-24 07:18:32 -05:00
transition [ :reopened , :opened ] = > :locked
end
2014-06-26 08:09:17 -04:00
event :unlock_mr do
2014-01-24 07:18:32 -05:00
transition locked : :reopened
end
2014-12-05 08:49:25 -05:00
after_transition any = > :locked do | merge_request , transition |
merge_request . locked_at = Time . now
merge_request . save
end
2015-02-02 22:30:09 -05:00
after_transition locked : ( any - :locked ) do | merge_request , transition |
2014-12-05 08:49:25 -05:00
merge_request . locked_at = nil
merge_request . save
end
2013-02-18 03:40:56 -05:00
state :opened
state :reopened
state :closed
state :merged
2014-01-24 07:18:32 -05:00
state :locked
2013-02-18 03:40:56 -05:00
end
2013-02-20 08:15:01 -05:00
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
2015-03-19 16:51:16 -04:00
transition [ :unchecked , :cannot_be_merged ] = > :can_be_merged
2013-02-20 08:15:01 -05:00
end
event :mark_as_unmergeable do
2015-03-19 16:51:16 -04:00
transition [ :unchecked , :can_be_merged ] = > :cannot_be_merged
2013-02-20 08:15:01 -05:00
end
2013-02-26 03:38:40 -05:00
state :unchecked
2013-02-20 08:15:01 -05:00
state :can_be_merged
state :cannot_be_merged
2015-03-23 12:30:19 -04:00
around_transition do | merge_request , transition , block |
merge_request . record_timestamps = false
begin
block . call
ensure
merge_request . record_timestamps = true
end
end
2013-02-20 08:15:01 -05:00
end
2012-03-30 01:05:04 -04:00
2013-12-12 05:20:54 -05:00
validates :source_project , presence : true , unless : :allow_broken
2012-10-08 20:10:04 -04:00
validates :source_branch , presence : true
2013-04-25 10:15:33 -04:00
validates :target_project , presence : true
2012-10-08 20:10:04 -04:00
validates :target_branch , presence : true
2013-04-25 10:15:33 -04:00
validate :validate_branches
2014-04-02 14:51:53 -04:00
validate :validate_fork
2011-11-28 02:39:43 -05:00
2013-06-06 17:22:36 -04:00
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 ) }
2013-02-18 03:40:56 -05:00
scope :merged , - > { with_state ( :merged ) }
2013-04-25 10:15:33 -04:00
scope :by_branch , - > ( branch_name ) { where ( " (source_branch LIKE :branch) OR (target_branch LIKE :branch) " , branch : branch_name ) }
2013-02-20 08:37:20 -05:00
scope :cared , - > ( user ) { where ( 'assignee_id = :user OR author_id = :user' , user : user . id ) }
2013-02-26 03:38:40 -05:00
scope :by_milestone , - > ( milestone ) { where ( milestone_id : milestone ) }
2013-04-25 10:15:33 -04:00
scope :in_projects , - > ( project_ids ) { where ( " source_project_id in (:project_ids) OR target_project_id in (:project_ids) " , project_ids : project_ids ) }
2013-08-08 10:29:31 -04:00
scope :of_projects , - > ( ids ) { where ( target_project_id : ids ) }
2013-02-21 07:11:24 -05:00
# Closed scope for merge request should return
# both merged and closed mr's
scope :closed , - > { with_states ( :closed , :merged ) }
2014-06-05 09:28:13 -04:00
scope :declined , - > { with_states ( :closed ) }
2013-02-21 07:11:24 -05:00
2012-03-13 17:54:49 -04:00
def validate_branches
2014-01-22 08:20:20 -05:00
if target_project == source_project && target_branch == source_branch
2013-04-25 10:15:33 -04:00
errors . add :branch_conflict , " You can not use same project/branch for source and target "
2012-03-13 17:54:49 -04:00
end
2013-06-14 08:03:22 -04:00
2013-06-14 10:19:26 -04:00
if opened? || reopened?
2013-06-26 16:45:57 -04:00
similar_mrs = self . target_project . merge_requests . where ( source_branch : source_branch , target_branch : target_branch , source_project_id : source_project . id ) . opened
2013-06-14 10:19:26 -04:00
similar_mrs = similar_mrs . where ( 'id not in (?)' , self . id ) if self . id
if similar_mrs . any?
2014-08-18 14:09:09 -04:00
errors . add :validate_branches ,
" Cannot Create: This merge request already exists: #{
similar_mrs . pluck ( :title )
} "
2013-06-14 10:19:26 -04:00
end
2013-06-14 08:03:22 -04:00
end
2012-03-13 17:54:49 -04:00
end
2014-04-02 14:51:53 -04:00
def validate_fork
2014-04-03 03:36:10 -04:00
return true unless target_project && source_project
if target_project == source_project
2014-04-02 14:51:53 -04:00
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
2014-08-18 14:09:09 -04:00
errors . add :validate_fork ,
'Source project is not a fork of target project'
2014-04-02 14:51:53 -04:00
end
end
end
2014-02-18 13:17:26 -05:00
def update_merge_request_diff
if source_branch_changed? || target_branch_changed?
reload_code
mark_as_unchecked
end
end
2012-03-15 19:45:46 -04:00
def reload_code
2014-03-11 17:50:02 -04:00
if merge_request_diff && open ?
2014-01-24 07:38:02 -05:00
merge_request_diff . reload_content
end
2012-03-15 19:45:46 -04:00
end
2012-03-30 01:05:04 -04:00
def check_if_can_be_merged
2013-02-20 08:15:01 -05:00
if Gitlab :: Satellite :: MergeAction . new ( self . author , self ) . can_be_merged?
mark_as_mergeable
else
mark_as_unmergeable
end
2012-03-29 17:27:42 -04:00
end
2012-03-15 17:32:00 -04:00
def merge_event
2013-04-25 10:15:33 -04:00
self . target_project . events . where ( target_id : self . id , target_type : " MergeRequest " , action : Event :: MERGED ) . last
2012-03-15 17:32:00 -04:00
end
2012-03-15 19:45:46 -04:00
def closed_event
2013-04-25 10:15:33 -04:00
self . target_project . events . where ( target_id : self . id , target_type : " MergeRequest " , action : Event :: CLOSED ) . last
2012-03-15 19:45:46 -04:00
end
2014-01-14 03:47:28 -05:00
def automerge! ( current_user , commit_message = nil )
2015-01-06 19:24:47 -05:00
MergeRequests :: AutoMergeService .
new ( target_project , current_user ) .
execute ( self , commit_message )
2012-03-30 01:05:04 -04:00
end
2012-07-04 18:26:23 -04:00
2014-02-28 15:43:16 -05:00
def open?
opened? || reopened?
end
2012-10-04 18:25:40 -04:00
def mr_and_commit_notes
2014-01-22 08:54:53 -05:00
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
commit_ids = commits . last ( commits_for_notes_limit ) . map ( & :id )
2013-10-07 07:49:01 -04:00
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
)
2012-10-04 18:25:40 -04:00
end
2013-06-06 17:22:36 -04:00
2012-11-22 18:55:57 -05:00
# Returns the raw diff for this merge request
#
# see "git diff"
2013-04-25 10:15:33 -04:00
def to_diff ( current_user )
Gitlab :: Satellite :: MergeAction . new ( current_user , self ) . diff_in_satellite
2012-11-22 18:55:57 -05:00
end
# Returns the commit as a series of email patches.
#
# see "git format-patch"
2013-04-25 10:15:33 -04:00
def to_patch ( current_user )
Gitlab :: Satellite :: MergeAction . new ( current_user , self ) . format_patch
2012-11-22 18:55:57 -05:00
end
2012-12-10 22:14:05 -05:00
2014-09-15 03:10:35 -04:00
def hook_attrs
attrs = {
source : source_project . hook_attrs ,
target : target_project . hook_attrs ,
last_commit : nil
}
unless last_commit . nil?
attrs . merge! ( last_commit : last_commit . hook_attrs ( source_project ) )
end
attributes . merge! ( attrs )
end
2013-04-25 10:15:33 -04:00
def for_fork?
target_project != source_project
end
2013-08-19 15:10:56 -04:00
def project
target_project
end
2013-05-30 19:16:49 -04:00
# Return the set of issues that will be closed if this merge request is accepted.
2015-03-27 07:19:48 -04:00
def closes_issues ( current_user = self . author )
2013-05-30 19:16:49 -04:00
if target_branch == project . default_branch
2015-03-27 07:19:48 -04:00
issues = commits . flat_map { | c | c . closes_issues ( project , current_user ) }
2015-04-03 12:03:26 -04:00
issues . push ( * Gitlab :: ClosingIssueExtractor . new ( project , current_user ) .
closed_by_message ( description ) )
2014-06-13 10:19:08 -04:00
issues . uniq . sort_by ( & :id )
2013-05-30 19:16:49 -04:00
else
[ ]
end
end
# Mentionable override.
def gfm_reference
" merge request ! #{ iid } "
end
2013-12-12 04:34:42 -05:00
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
2014-02-15 07:36:58 -05:00
def source_project_namespace
if source_project && source_project . namespace
source_project . namespace . path
else
" (removed) "
end
end
2014-05-08 08:37:36 -04:00
def target_project_namespace
if target_project && target_project . namespace
target_project . namespace . path
else
" (removed) "
end
end
2013-12-12 04:34:42 -05:00
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
2013-12-13 14:40:45 -05:00
# 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
2014-07-16 14:44:24 -04:00
Event . reset_event_cache_for ( self )
2013-12-13 14:40:45 -05:00
end
2014-01-13 10:20:30 -05:00
def merge_commit_message
message = " Merge branch ' #{ source_branch } ' into ' #{ target_branch } ' "
message << " \n \n "
2014-01-14 03:47:28 -05:00
message << title . to_s
2014-01-13 10:20:30 -05:00
message << " \n \n "
2014-01-14 03:47:28 -05:00
message << description . to_s
2014-07-01 15:54:19 -04:00
message << " \n \n "
message << " See merge request ! #{ iid } "
2014-01-14 03:47:28 -05:00
message
2014-01-13 10:20:30 -05:00
end
2014-01-31 06:24:38 -05:00
# Return array of possible target branches
2015-01-18 10:29:37 -05:00
# depends on target project of MR
2014-01-31 06:24:38 -05:00
def target_branches
if target_project . nil?
[ ]
else
target_project . repository . branch_names
end
end
# Return array of possible source branches
2015-01-18 10:29:37 -05:00
# depends on source project of MR
2014-01-31 06:24:38 -05:00
def source_branches
if source_project . nil?
[ ]
else
source_project . repository . branch_names
end
end
2014-12-05 09:02:08 -05:00
def locked_long_ago?
2015-04-15 02:11:13 -04:00
return false unless locked?
locked_at . nil? || locked_at < ( Time . now - 1 . day )
2014-12-05 09:02:08 -05:00
end
2011-11-28 02:39:43 -05:00
end