rosa-build/app/models/pull_request.rb

212 lines
6.3 KiB
Ruby
Raw Normal View History

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
validate :uniq_merge
2012-10-03 12:36:04 +01:00
validates_each :from_ref, :to_ref do |record, attr, value|
check_ref record, attr, value
end
2012-05-22 19:23:00 +01:00
before_create :clean_dir
after_destroy :clean_dir
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
scope :needed_checking, includes(:issue).where(:issues => {:status => ['open', 'blocked', 'ready']})
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 :already do
transition [:ready, :open, :blocked] => :already
end
2012-04-16 19:40:50 +01:00
event :block do
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
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
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")
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
@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
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
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)
end
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))
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
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-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
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
end
end
end
def clean_dir
FileUtils.rm_rf path
end
2012-04-16 19:40:50 +01:00
end