2012-04-28 18:28:57 +01:00
class PullRequest < ActiveRecord :: Base
2012-06-04 18:00:19 +01:00
STATUSES = %w( ready already blocked merged closed )
2012-04-28 18:28:57 +01:00
belongs_to :issue , :autosave = > true , :dependent = > :destroy , :touch = > true , :validate = > true
2012-10-03 12:36:04 +01:00
belongs_to :to_project , :class_name = > 'Project' , :foreign_key = > 'to_project_id'
belongs_to :from_project , :class_name = > 'Project' , :foreign_key = > 'from_project_id'
2012-07-10 17:58:39 +01:00
delegate :user , :user_id , :title , :body , :serial_id , :assignee , :status , :to_param ,
2012-08-07 19:08:02 +01:00
:created_at , :updated_at , :comments , :status = , :to = > :issue , :allow_nil = > true
2012-05-31 17:56:27 +01:00
2012-08-07 19:06:49 +01:00
validate :uniq_merge
2012-10-03 12:36:04 +01:00
validates_each :from_ref , :to_ref do | record , attr , value |
2012-08-07 17:24:47 +01:00
check_ref record , attr , value
2012-05-23 17:09:11 +01:00
end
2012-05-22 19:23:00 +01:00
2012-05-21 14:24:16 +01:00
before_create :clean_dir
2012-05-23 17:09:11 +01:00
after_destroy :clean_dir
2012-05-21 14:24:16 +01:00
2012-05-31 17:56:27 +01:00
accepts_nested_attributes_for :issue
2012-10-03 12:36:04 +01:00
attr_accessible :issue_attributes , :to_ref , :from_ref
2012-05-31 17:56:27 +01:00
2012-06-09 10:09:38 +01:00
scope :needed_checking , includes ( :issue ) . where ( :issues = > { :status = > [ 'open' , 'blocked' , 'ready' ] } )
2012-04-28 18:28:57 +01:00
2012-05-29 11:09:37 +01:00
state_machine :status , :initial = > :open do
2012-04-18 18:08:53 +01:00
#after_transition [:ready, :blocked] => [:merged, :closed] do |pull, transition|
# FileUtils.rm_rf(pull.path) # What about diff?
#end
2012-04-16 19:40:50 +01:00
2012-04-18 18:08:53 +01:00
event :ready do
2012-05-23 17:09:11 +01:00
transition [ :ready , :open , :blocked ] = > :ready
2012-04-16 19:40:50 +01:00
end
2012-07-09 19:41:39 +01:00
event :already do
transition [ :ready , :open , :blocked ] = > :already
end
2012-04-16 19:40:50 +01:00
event :block do
2012-07-09 19:41:39 +01:00
transition [ :ready , :open , :blocked ] = > :blocked
2012-04-16 19:40:50 +01:00
end
2012-04-18 18:08:53 +01:00
event :merging do
transition :ready = > :merged
2012-04-16 19:40:50 +01:00
end
event :close do
2012-07-09 19:41:39 +01:00
transition [ :ready , :open , :blocked ] = > :closed
2012-04-18 18:08:53 +01:00
end
event :reopen do
transition :closed = > :open
2012-04-16 19:40:50 +01:00
end
end
2012-06-28 14:47:29 +01:00
def check ( do_transaction = true )
2012-09-28 13:26:12 +01:00
res = merge
new_status = case res
2012-06-28 14:47:29 +01:00
when / Already up-to-date /
'already'
2012-09-28 15:31:21 +01:00
when / Merge made by /
2012-06-28 14:47:29 +01:00
system ( " cd #{ path } && git reset --hard HEAD^ " ) # remove merge commit
'ready'
when / Automatic merge failed /
system ( " cd #{ path } && git reset --hard HEAD " ) # clean git index
'block'
else
2012-09-28 13:26:12 +01:00
raise res
2012-06-28 14:47:29 +01:00
end
if do_transaction
if new_status == 'already'
ready ; merging
else
send ( new_status )
end
2012-05-24 18:10:49 +01:00
else
2012-06-28 14:47:29 +01:00
self . status = new_status == 'block' ? 'blocked' : new_status
2012-05-24 18:10:49 +01:00
end
end
2012-04-18 18:08:53 +01:00
def merge! ( who )
2012-08-07 14:21:13 +01:00
return false unless can_merging?
2012-04-18 18:08:53 +01:00
Dir . chdir ( path ) do
2012-04-20 16:17:43 +01:00
system " git config user.name \" #{ who . uname } \" && git config user.email \" #{ who . email } \" "
2012-04-18 18:08:53 +01:00
if merge
system ( " git push origin HEAD " )
2012-06-26 10:43:32 +01:00
system ( " git reset --hard HEAD^ " ) # for diff maybe FIXME
2012-06-28 11:44:55 +01:00
set_user_and_time who
merging
2012-04-18 18:08:53 +01:00
end
end
2012-04-16 19:40:50 +01:00
end
def path
2012-10-03 12:36:04 +01:00
filename = [ id , from_project . owner . uname , from_project . name ] . compact . join ( '-' )
File . join ( APP_CONFIG [ 'root_path' ] , 'pull_requests' , to_project . owner . uname , to_project . name , filename )
2012-04-16 19:40:50 +01:00
end
2012-05-12 17:23:39 +01:00
def head_branch
2012-10-03 12:36:04 +01:00
if to_project != from_project
" head_ #{ from_ref } "
2012-05-12 17:23:39 +01:00
else
2012-10-03 12:36:04 +01:00
from_ref
2012-05-12 17:23:39 +01:00
end
end
2012-06-07 18:23:28 +01:00
def common_ancestor
return @common_ancestor if @common_ancestor
repo = Grit :: Repo . new ( path )
2012-10-03 12:36:04 +01:00
base_commit = repo . commits ( to_ref ) . first
2012-06-07 18:23:28 +01:00
head_commit = repo . commits ( head_branch ) . first
2012-10-01 18:52:18 +01:00
@common_ancestor = repo . commit ( repo . git . merge_base ( { } , base_commit , head_commit ) ) || base_commit
2012-06-07 18:23:28 +01:00
end
def diff_stats ( repo , a , b )
2012-05-29 18:09:43 +01:00
stats = [ ]
Dir . chdir ( path ) do
2012-06-07 20:19:18 +01:00
lines = repo . git . native ( :diff , { :numstat = > true , :M = > true } , " #{ a . id } ... #{ b . id } " ) . split ( " \n " )
2012-05-29 18:09:43 +01:00
while ! lines . empty?
files = [ ]
while lines . first =~ / ^([- \ d]+) \ s+([- \ d]+) \ s+(.+) /
2012-06-07 20:19:18 +01:00
additions , deletions , filename = lines . shift . gsub ( ' => ' , '=>' ) . split
2012-05-29 18:09:43 +01:00
additions , deletions = additions . to_i , deletions . to_i
2012-05-30 19:08:07 +01:00
stat = Grit :: DiffStat . new filename , additions , deletions
2012-05-29 18:09:43 +01:00
stats << stat
end
end
stats
end
end
2012-08-06 19:24:31 +01:00
# FIXME maybe move to warpc/grit?
2012-06-07 18:23:28 +01:00
def diff ( repo , a , b )
2012-06-07 20:19:18 +01:00
diff = repo . git . native ( 'diff' , { :M = > true } , " #{ a } ... #{ b } " )
2012-06-07 18:23:28 +01:00
if diff =~ / diff --git a /
diff = diff . sub ( / .*?(diff --git a) /m , '\1' )
else
diff = ''
end
Grit :: Diff . list_from_string ( repo , diff )
end
2012-07-12 15:15:28 +01:00
def set_user_and_time user
issue . closed_at = Time . now . utc
issue . closer = user
end
2012-08-07 17:24:47 +01:00
def self . check_ref ( record , attr , value )
2012-10-03 12:36:04 +01:00
project = attr == :from_ref ? record . from_project : record . to_project
2012-08-08 11:33:48 +01:00
record . errors . add attr , I18n . t ( 'projects.pull_requests.wrong_ref' ) unless project . repo . branches_and_tags . map ( & :name ) . include? ( value )
2012-08-07 17:24:47 +01:00
end
2012-08-07 19:06:49 +01:00
def uniq_merge
2012-10-03 12:36:04 +01:00
if to_project . pull_requests . needed_checking . where ( :from_project_id = > from_project , :to_ref = > to_ref , :from_ref = > from_ref ) . where ( 'pull_requests.id <> :id or :id is null' , :id = > id ) . count > 0
errors . add ( :base_branch , I18n . t ( 'projects.pull_requests.duplicate' , :from_ref = > from_ref ) )
2012-08-07 19:06:49 +01:00
end
end
2012-05-05 17:57:12 +01:00
protected
2012-04-16 19:40:50 +01:00
def merge
clone
2012-10-03 12:36:04 +01:00
message = " Merge pull request # #{ serial_id } from #{ from_project . name_with_owner } : #{ from_ref } \r \n #{ title } "
%x( cd #{ path } && git checkout #{ to_ref } && git merge --no-ff #{ head_branch } -m ' #{ message } ' )
2012-04-16 19:40:50 +01:00
end
def clone
2012-04-18 18:08:53 +01:00
git = Grit :: Git . new ( path )
2012-04-16 19:40:50 +01:00
unless git . exist?
2012-08-10 17:42:34 +01:00
#~ FileUtils.mkdir_p(path)
2012-10-03 12:36:04 +01:00
#~ system("git clone --local --no-hardlinks #{to_project.path} #{path}")
options = { :bare = > false , :shared = > false , :branch = > to_ref } # shared?
2012-08-10 17:42:34 +01:00
git . fs_mkdir ( '..' )
2012-10-03 12:36:04 +01:00
git . clone ( options , to_project . path , path )
if to_project != from_project
2012-04-28 18:28:57 +01:00
Dir . chdir ( path ) do
2012-10-03 12:36:04 +01:00
system 'git' , 'remote' , 'add' , 'head' , from_project . path
2012-04-28 18:28:57 +01:00
end
end
2012-08-10 18:06:34 +01:00
clean
2012-04-19 20:08:33 +01:00
end
2012-04-28 18:28:57 +01:00
2012-04-19 20:08:33 +01:00
Dir . chdir ( path ) do
2012-10-03 12:36:04 +01:00
system 'git' , 'checkout' , to_ref
system 'git' , 'pull' , 'origin' , to_ref
if to_project == from_project
system 'git' , 'checkout' , from_ref
system 'git' , 'pull' , 'origin' , from_ref
2012-04-28 18:28:57 +01:00
else
2012-10-03 12:36:04 +01:00
system 'git' , 'fetch' , 'head' , " + #{ from_ref } : #{ head_branch } "
2012-04-16 19:40:50 +01:00
end
end
# TODO catch errors
end
2012-05-21 14:24:16 +01:00
def clean
Dir . chdir ( path ) do
2012-10-03 12:36:04 +01:00
to_project . repo . branches . each { | branch | system 'git' , 'checkout' , branch . name }
system 'git' , 'checkout' , to_ref
2012-05-21 14:24:16 +01:00
2012-10-03 12:36:04 +01:00
to_project . repo . branches . each do | branch |
system 'git' , 'branch' , '-D' , branch . name unless [ to_ref , head_branch ] . include? branch . name
2012-05-21 14:24:16 +01:00
end
2012-10-03 12:36:04 +01:00
to_project . repo . tags . each do | tag |
system 'git' , 'tag' , '-d' , tag . name unless [ to_ref , head_branch ] . include? tag . name
2012-05-21 14:24:16 +01:00
end
end
end
def clean_dir
FileUtils . rm_rf path
end
2012-04-16 19:40:50 +01:00
end