rosa-build/app/models/pull_request.rb

277 lines
8.2 KiB
Ruby
Raw Normal View History

2012-04-28 18:28:57 +01:00
class PullRequest < ActiveRecord::Base
STATUSES = [
STATUS_OPEN = 'open',
STATUS_READY = 'ready',
STATUS_ALREADY = 'already',
STATUS_BLOCKED = 'blocked',
STATUS_MERGED = 'merged',
STATUS_CLOSED = 'closed'
]
belongs_to :issue,
autosave: true,
dependent: :destroy,
touch: true,
validate: true
belongs_to :to_project,
class_name: 'Project',
foreign_key: 'to_project_id'
belongs_to :from_project,
class_name: 'Project',
foreign_key: 'from_project_id'
delegate :user,
:user_id,
:title,
:body,
:serial_id,
:assignee,
:status,
:to_param,
:created_at,
:updated_at,
:comments,
:status=,
to: :issue, allow_nil: true
2012-05-31 17:56:27 +01:00
2014-01-21 04:51:49 +00:00
validates :from_project, :to_project, presence: true
validate :uniq_merge, if: ->(pull) { pull.to_project.present? }
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
2012-10-27 19:46:04 +01:00
before_create :set_add_data
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: [STATUS_OPEN, STATUS_BLOCKED, STATUS_READY] }) }
scope :not_closed_or_merged, -> { needed_checking }
scope :closed_or_merged, -> { where(issues: { status: [STATUS_CLOSED, STATUS_MERGED] }) }
2012-04-28 18:28:57 +01:00
2014-01-21 04:51:49 +00:00
state_machine :status, initial: :open do
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
2014-01-21 04:51:49 +00:00
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
2014-01-21 04:51:49 +00:00
transition closed: :open
2012-04-16 19:40:50 +01:00
end
end
def update_relations(old_from_project_name = nil)
2014-01-21 04:51:49 +00:00
FileUtils.mv path(old_from_project_name), path, force: true if old_from_project_name
return unless Dir.exists?(path)
Dir.chdir(path) do
system 'git', 'remote', 'set-url', 'origin', to_project.path
system 'git', 'remote', 'set-url', 'head', from_project.path if cross_pull?
2014-01-21 04:51:49 +00:00
end
end
2014-04-15 19:41:06 +01:00
later :update_relations, queue: :middle
2013-07-12 15:21:46 +01:00
def cross_pull?
from_project_id != to_project_id
end
2012-06-28 14:47:29 +01:00
def check(do_transaction = true)
if do_transaction && !valid?
issue.set_close nil
2014-01-21 04:51:49 +00:00
issue.save(validate: false) # FIXME remove this hack
return false
end
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
2012-10-15 18:42:28 +01:00
new_status == 'already' ? (ready; merging) : send(new_status)
self.update_inline_comments
2012-05-24 18:10:49 +01:00
else
self.status = new_status == 'block' ? STATUS_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
old_commit = to_project.repo.commits(to_ref).first
2013-03-18 18:27:26 +00:00
commit = repo.commits(to_ref).first
system "git config user.name \"#{who.uname}\" && git config user.email \"#{who.email}\""
2013-03-18 18:27:26 +00:00
res = merge
if commit.id != repo.commits(to_ref).first.id
res2 = %x(export GL_ID=user-#{who.id} GL_REPO_NAME=#{to_project.path} && git push origin HEAD)
system("git reset --hard HEAD^") # for diff maybe FIXME
if old_commit.id == to_project.repo.commits(to_ref).first.id
raise "merge result pull_request #{id}: #{$?.exitstatus}; #{res2}; #{res}"
end
2012-06-28 11:44:55 +01:00
set_user_and_time who
merging
2013-03-18 18:27:26 +00:00
else # Try to catch no merge errors
raise "merge result pull_request #{id}: #{res}"
2012-04-18 18:08:53 +01:00
end
end
2012-04-16 19:40:50 +01:00
end
def path(suffix = from_project_name)
last_part = [id, from_project_owner_uname, suffix].compact.join('-')
File.join(APP_CONFIG['git_path'], "#{new_record? ? 'temp_' : ''}pull_requests", to_project.owner.uname, to_project.name, last_part)
2012-04-16 19:40:50 +01:00
end
2012-10-18 12:46:57 +01:00
def from_branch
cross_pull? ? "head_#{from_ref}" : from_ref
2012-05-12 17:23:39 +01:00
end
2012-06-07 18:23:28 +01:00
def common_ancestor
return @common_ancestor if @common_ancestor
2012-10-03 12:36:04 +01:00
base_commit = repo.commits(to_ref).first
@common_ancestor = repo.commit(repo.git.merge_base({}, base_commit, from_commit)) || base_commit
2012-06-07 18:23:28 +01:00
end
alias_method :to_commit, :common_ancestor
2012-06-07 18:23:28 +01:00
def diff_stats
@diff_stats ||= repo.diff_stats(to_commit.id, from_commit.id)
2012-05-29 18:09:43 +01:00
end
def diff
2015-04-07 18:14:18 +01:00
@diff ||= repo.diff(to_commit.id, from_commit.id)
2012-06-07 18:23:28 +01:00
end
2012-07-12 15:15:28 +01:00
def set_user_and_time user
issue.closed_at = Time.now.utc
issue.closer = user
2012-07-12 15:15:28 +01:00
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-10-29 15:33:20 +00:00
return if project.blank?
if record.to_project.repo.branches.count > 0
2012-10-29 15:33:20 +00:00
record.errors.add attr, I18n.t('projects.pull_requests.wrong_ref') unless project.repo.branches_and_tags.map(&:name).include?(value)
else
record.errors.add attr, I18n.t('projects.pull_requests.empty_repo')
end
end
def uniq_merge
2012-10-29 15:33:20 +00:00
if to_project && to_project.pull_requests.needed_checking
2014-01-21 04:51:49 +00:00
.where(from_project_id: from_project_id,
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
def repo
@repo ||= Grit::Repo.new(path)
end
def from_commit
repo.commits(from_branch).first
end
2012-05-05 17:57:12 +01:00
protected
2012-04-16 19:40:50 +01:00
def merge
clone
2012-10-27 19:04:28 +01:00
message = "Merge pull request ##{serial_id} from #{from_project_owner_uname}/#{from_project_name}:#{from_ref}\r\n #{title}"
%x(cd #{path} && git checkout #{to_ref.shellescape} && git merge --no-ff #{from_branch.shellescape} -m #{message.shellescape})
2012-04-16 19:40:50 +01:00
end
def clone
return if from_project.nil?
2012-04-18 18:08:53 +01:00
git = Grit::Git.new(path)
2012-11-14 15:46:59 +00:00
if new_record? || !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}")
2014-01-21 04:51:49 +00:00
options = {bare: false, shared: false, branch: to_ref} # shared?
2012-11-14 15:46:59 +00:00
`rm -rf #{path}`
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 cross_pull?
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
2013-04-02 10:19:09 +01:00
#clean # Need testing
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
system 'git', 'tag', '-d', from_ref, to_ref
system 'git fetch --tags && git fetch --all'
2013-03-07 12:01:08 +00:00
tags, head = repo.tags.map(&:name), to_project == from_project ? 'origin' : 'head'
system 'git', 'checkout', to_ref
unless tags.include? to_ref
system 'git', 'reset', '--hard', "origin/#{to_ref}"
end
unless tags.include? from_ref
2013-06-18 03:11:20 +01:00
system 'git', 'branch', '-D', from_branch
system 'git', 'fetch', head, "+#{from_ref}:#{from_branch}"
system 'git', 'checkout', to_ref
2012-04-16 19:40:50 +01:00
end
end
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|
2012-10-18 12:46:57 +01:00
system 'git', 'branch', '-D', branch.name unless [to_ref, from_branch].include? branch.name
end
2012-10-03 12:36:04 +01:00
to_project.repo.tags.each do |tag|
2012-10-18 12:46:57 +01:00
system 'git', 'tag', '-d', tag.name unless [to_ref, from_branch].include? tag.name
end
end
end
def clean_dir
FileUtils.rm_rf path
end
def update_inline_comments
self.comments.each do |c|
if c.data.present? # maybe need add new column 'actual'?
c.actual_inline_comment? diff, true
c.save
end
end
end
2012-10-27 19:46:04 +01:00
def set_add_data
self.from_project_owner_uname = from_project.owner.uname
self.from_project_name = from_project.name
2012-10-27 19:46:04 +01:00
end
2012-04-16 19:40:50 +01:00
end