rosa-build/app/models/pull_request.rb

197 lines
5.6 KiB
Ruby
Raw Normal View History

2012-04-28 18:28:57 +01:00
class PullRequest < ActiveRecord::Base
belongs_to :issue, :autosave => true, :dependent => :destroy, :touch => true, :validate => true
belongs_to :base_project, :class_name => 'Project', :foreign_key => 'base_project_id'
belongs_to :head_project, :class_name => 'Project', :foreign_key => 'head_project_id'
delegate :user, :title, :body, :serial_id, :assignee, :status, :to_param, :to => :issue, :allow_nil => true
2012-04-28 18:28:57 +01:00
accepts_nested_attributes_for :issue
#attr_accessible #FIXME disable for development
validate :uniq_merge
validates_each :head_ref, :base_ref do |record, attr, value|
project = attr == :head_ref ? record.head_project : record.base_project
if !((project.branches + project.tags).map(&:name).include?(value) || project.git_repository.commits.map(&:id).include?(value))
record.errors.add attr, I18n.t('projects.pull_requests.wrong_ref')
end
end
2012-05-22 19:23:00 +01:00
before_create :clean_dir
after_destroy :clean_dir
scope :needed_checking, includes(:issue).where(:issues => {:status => ['open', 'blocked', 'ready', 'already']})
2012-04-28 18:28:57 +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
transition [:ready, :open, :blocked] => :ready
2012-04-16 19:40:50 +01:00
end
event :block do
transition [:blocked, :open, :ready] => :blocked
2012-04-16 19:40:50 +01:00
end
2012-04-20 17:23:28 +01:00
event :already do
transition [:open, :blocked, :ready] => :already
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-04-18 18:08:53 +01:00
transition [:open, :ready, :blocked] => :closed
end
event :reopen do
transition :closed => :open
2012-04-16 19:40:50 +01:00
end
end
def status=(value)
issue.status = value
2012-05-10 10:42:25 +01:00
end
2012-04-16 19:40:50 +01:00
def can_merge?
status == 'ready'
2012-04-18 18:08:53 +01:00
end
def check
2012-04-20 17:23:28 +01:00
ret = merge
if ret =~ /Already up-to-date/
already
2012-05-02 16:36:39 +01:00
elsif ret =~ /Merge made by the 'recursive' strategy/
2012-04-18 18:08:53 +01:00
system("cd #{path} && git reset --hard HEAD^") # remove merge commit
ready
2012-05-02 16:36:39 +01:00
elsif ret =~ /Automatic merge failed/
2012-04-18 18:08:53 +01:00
system("cd #{path} && git reset --hard HEAD")
block
2012-05-02 16:36:39 +01:00
else
2012-05-10 10:42:25 +01:00
raise ret
2012-04-18 18:08:53 +01:00
end
end
2012-05-24 18:10:49 +01:00
def soft_check
ret = merge
if ret =~ /Already up-to-date/
'already'
elsif ret =~ /Merge made by the 'recursive' strategy/
system("cd #{path} && git reset --hard HEAD^") # remove merge commit
'ready'
elsif ret =~ /Automatic merge failed/
system("cd #{path} && git reset --hard HEAD")
'block'
else
raise ret
end
end
2012-04-18 18:08:53 +01:00
def merge!(who)
return false unless can_merge?
Dir.chdir(path) do
system "git config user.name \"#{who.uname}\" && git config user.email \"#{who.email}\""
2012-04-18 18:08:53 +01:00
if merge
merging
system("git push origin HEAD")
2012-05-17 17:47:36 +01:00
system("git reset --hard HEAD") # for diff maybe FIXME
2012-04-18 18:08:53 +01:00
end
end
2012-04-16 19:40:50 +01:00
end
2012-04-28 18:28:57 +01:00
def self.default_base_project(project)
project.is_root? ? project : project.root
end
2012-04-16 19:40:50 +01:00
def path
2012-05-17 17:47:36 +01:00
filename = [id, base_ref, head_project.owner.uname, head_project.name, head_ref].compact.join('-')
2012-04-16 19:40:50 +01:00
if Rails.env == "production"
2012-05-17 17:47:36 +01:00
File.join('/srv/rosa_build/shared/tmp', "pull_requests", base_project.owner.uname, base_project.name, filename)
2012-04-16 19:40:50 +01:00
else
2012-05-17 17:47:36 +01:00
File.join(Rails.root, "tmp", Rails.env, "pull_requests", base_project.owner.uname, base_project.name, filename)
2012-04-16 19:40:50 +01:00
end
end
2012-05-12 17:23:39 +01:00
def head_branch
if base_project != head_project
"head_#{head_ref}"
else
head_ref
end
end
2012-05-29 18:09:43 +01:00
def diff_stats
stats = []
Dir.chdir(path) do
lines = %x(git diff --numstat #{base_ref} #{head_ref}).split("\n")
while !lines.empty?
files = []
while lines.first =~ /^([-\d]+)\s+([-\d]+)\s+(.+)/
additions, deletions, filename = lines.shift.split
additions, deletions = additions.to_i, deletions.to_i
stat = Grit::DiffStat.new filename, additions, deletions
2012-05-29 18:09:43 +01:00
stats << stat
end
end
stats
end
end
2012-05-05 17:57:12 +01:00
protected
2012-04-16 19:40:50 +01:00
def merge
clone
2012-05-12 17:23:39 +01:00
%x(cd #{path} && git checkout #{base_ref} && git merge --no-ff #{head_branch}) #FIXME need sanitize branch name!
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?
FileUtils.mkdir_p(path)
2012-04-28 18:28:57 +01:00
system("git clone --local --no-hardlinks #{base_project.path} #{path}")
if base_project != head_project
Dir.chdir(path) do
system 'git', 'remote', 'add', 'head', head_project.path
end
end
2012-04-19 20:08:33 +01:00
end
2012-04-28 18:28:57 +01:00
clean
2012-04-19 20:08:33 +01:00
Dir.chdir(path) do
2012-04-28 18:28:57 +01:00
system 'git', 'checkout', base_ref
system 'git', 'pull', 'origin', base_ref
if base_project == head_project
system 'git', 'checkout', head_ref
system 'git', 'pull', 'origin', head_ref
else
2012-05-12 17:23:39 +01:00
system 'git', 'fetch', 'head', "+#{head_ref}:#{head_branch}"
2012-04-16 19:40:50 +01:00
end
end
# TODO catch errors
end
def clean
Dir.chdir(path) do
base_project.branches.each {|branch| system 'git', 'checkout', branch.name}
system 'git', 'checkout', base_ref
base_project.branches.each do |branch|
system 'git', 'branch', '-D', branch.name unless [base_ref, head_branch].include? branch.name
end
base_project.tags.each do |tag|
system 'git', 'tag', '-d', tag.name unless [base_ref, head_branch].include? tag.name
end
end
end
def uniq_merge
2012-05-22 19:23:00 +01:00
if base_project.pull_requests.needed_checking.where(:head_project_id => head_project, :base_ref => base_ref, :head_ref => head_ref).where('pull_requests.id <> :id or :id is null', :id => id).count > 0
errors.add(:base_branch, I18n.t('projects.pull_requests.duplicate', :head_ref => head_ref))
end
end
def clean_dir
FileUtils.rm_rf path
end
2012-04-16 19:40:50 +01:00
end