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-10-29 15:33:20 +00:00
|
|
|
validates :from_project, :to_project, :presence => true
|
|
|
|
validate :uniq_merge, :if => Proc.new { |pull| pull.to_project.present? }
|
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-10-27 19:46:04 +01:00
|
|
|
before_create :set_add_data
|
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']})
|
2013-06-18 20:45:40 +01:00
|
|
|
scope :not_closed_or_merged, needed_checking
|
|
|
|
scope :closed_or_merged, where(:issues => {:status => ['closed', 'merged']})
|
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
|
|
|
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
|
|
|
|
|
2013-08-13 16:56:27 +01:00
|
|
|
def update_relations(old_from_project_name = nil)
|
2013-08-13 16:36:09 +01:00
|
|
|
FileUtils.mv path(old_from_project_name), path, :force => true if old_from_project_name
|
2013-08-13 16:05:16 +01:00
|
|
|
return unless Dir.exists?(path)
|
|
|
|
Dir.chdir(path) do
|
2013-08-13 19:33:48 +01:00
|
|
|
system 'git', 'remote', 'set-url', 'origin', to_project.path
|
|
|
|
system 'git', 'remote', 'set-url', 'head', from_project.path if cross_pull?
|
2013-08-13 16:05:16 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
later :update_relations, :queue => :clone_build
|
|
|
|
|
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)
|
2013-01-28 14:31:08 +00:00
|
|
|
if do_transaction && !valid?
|
|
|
|
issue.set_close nil
|
|
|
|
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)
|
2012-10-15 18:45:01 +01:00
|
|
|
self.update_inline_comments
|
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
|
2013-03-21 16:55:12 +00:00
|
|
|
old_commit = to_project.repo.commits(to_ref).first
|
2013-03-18 18:27:26 +00:00
|
|
|
commit = repo.commits(to_ref).first
|
2012-04-20 16:17:43 +01:00
|
|
|
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
|
2013-03-21 16:55:12 +00:00
|
|
|
res2 = %x(export GL_ID=user-#{who.id} && git push origin HEAD)
|
2012-06-26 10:43:32 +01:00
|
|
|
system("git reset --hard HEAD^") # for diff maybe FIXME
|
2013-03-21 16:55:12 +00:00
|
|
|
|
|
|
|
if old_commit.id == to_project.repo.commits(to_ref).first.id
|
2013-03-25 10:34:37 +00:00
|
|
|
raise "merge result pull_request #{id}: #{$?.exitstatus}; #{res2}; #{res}"
|
2013-03-21 16:55:12 +00:00
|
|
|
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
|
|
|
|
|
2013-08-13 16:36:09 +01:00
|
|
|
def path(suffix = from_project_name)
|
|
|
|
last_part = [id, from_project_owner_uname, suffix].compact.join('-')
|
2012-11-14 16:28:21 +00:00
|
|
|
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
|
2013-08-13 16:56:27 +01:00
|
|
|
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
|
2012-10-18 13:09:12 +01:00
|
|
|
@common_ancestor = repo.commit(repo.git.merge_base({}, base_commit, from_commit)) || base_commit
|
2012-06-07 18:23:28 +01:00
|
|
|
end
|
2012-10-18 13:09:12 +01:00
|
|
|
alias_method :to_commit, :common_ancestor
|
2012-06-07 18:23:28 +01:00
|
|
|
|
2012-10-18 13:09:12 +01:00
|
|
|
def diff_stats
|
2012-11-19 19:15:15 +00:00
|
|
|
@diff_stats ||= repo.diff_stats(to_commit.id, from_commit.id)
|
2012-05-29 18:09:43 +01:00
|
|
|
end
|
|
|
|
|
2012-08-06 19:24:31 +01:00
|
|
|
# FIXME maybe move to warpc/grit?
|
2012-10-18 13:09:12 +01:00
|
|
|
def diff
|
2012-11-19 19:15:15 +00: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
|
|
|
|
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-10-29 15:33:20 +00:00
|
|
|
return if project.blank?
|
2013-01-31 16:26:49 +00:00
|
|
|
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
|
2012-08-07 17:24:47 +01:00
|
|
|
end
|
|
|
|
|
2012-08-07 19:06:49 +01:00
|
|
|
def uniq_merge
|
2012-10-29 15:33:20 +00:00
|
|
|
if to_project && to_project.pull_requests.needed_checking
|
|
|
|
.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
|
2012-10-03 12:36:04 +01:00
|
|
|
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-10-16 11:19:53 +01:00
|
|
|
def repo
|
|
|
|
return @repo if @repo.present? #&& !id_changed?
|
|
|
|
@repo = Grit::Repo.new path
|
|
|
|
end
|
2012-10-16 14:22:19 +01:00
|
|
|
|
2012-10-18 13:09:12 +01:00
|
|
|
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}"
|
2013-06-11 05:03:04 +01:00
|
|
|
%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
|
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}")
|
|
|
|
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)
|
2013-08-13 16:56:27 +01:00
|
|
|
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
|
2013-01-28 16:10:26 +00:00
|
|
|
system 'git', 'tag', '-d', from_ref, to_ref
|
2013-03-18 18:26:45 +00:00
|
|
|
system 'git fetch --tags && git fetch --all'
|
2013-03-07 12:01:08 +00:00
|
|
|
|
2013-01-28 16:10:26 +00:00
|
|
|
tags, head = repo.tags.map(&:name), to_project == from_project ? 'origin' : 'head'
|
2013-01-31 16:26:49 +00:00
|
|
|
system 'git', 'checkout', to_ref
|
2013-01-28 16:10:26 +00:00
|
|
|
unless tags.include? to_ref
|
2013-06-17 17:14:39 +01:00
|
|
|
system 'git', 'reset', '--hard', "origin/#{to_ref}"
|
2013-01-28 16:10:26 +00:00
|
|
|
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}"
|
2013-06-11 05:03:04 +01:00
|
|
|
system 'git', 'checkout', to_ref
|
2012-04-16 19:40:50 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
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|
|
2012-10-18 12:46:57 +01:00
|
|
|
system 'git', 'branch', '-D', branch.name unless [to_ref, from_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|
|
2012-10-18 12:46:57 +01:00
|
|
|
system 'git', 'tag', '-d', tag.name unless [to_ref, from_branch].include? tag.name
|
2012-05-21 14:24:16 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def clean_dir
|
|
|
|
FileUtils.rm_rf path
|
|
|
|
end
|
2012-10-15 18:45:01 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
end
|
2012-04-16 19:40:50 +01:00
|
|
|
end
|