forked from tpope/hookup
/
hookup.rb
147 lines (126 loc) · 3.89 KB
/
hookup.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class Hookup
class Error < RuntimeError
end
class Failure < Error
end
EMPTY_DIR = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
def self.run(*argv)
new.run(*argv)
rescue Failure => e
puts e
exit 1
rescue Error => e
puts e
exit
end
def run(*argv)
if argv.empty?
install
else
command = argv.shift
begin
send(command.tr('-', '_'), *argv)
rescue NoMethodError
raise Error, "Unknown command #{command}"
rescue ArgumentError
raise Error, "Invalid arguments for #{command}"
end
end
end
def git_dir
unless @git_dir
@git_dir = %x{git rev-parse --git-dir}.chomp
raise Error, dir unless $?.success?
end
@git_dir
end
def install
append(File.join(git_dir, 'hooks', 'post-checkout'), 0777) do |body, f|
f.puts "#!/bin/bash" unless body
f.puts %(hookup post-checkout "$@") if body !~ /hookup/
end
append(File.join(git_dir, 'info', 'attributes')) do |body, f|
map = 'db/schema.rb merge=railsschema'
f.puts map unless body.to_s.include?(map)
end
system 'git', 'config', 'merge.railsschema.driver', 'hookup resolve-schema %A %O %B %L'
puts "Hooked up!"
end
def append(file, *args)
Dir.mkdir(File.dirname(file)) unless File.directory?(File.dirname(file))
body = File.read(file) if File.exist?(file)
File.open(file, 'a', *args) do |f|
yield body, f
end
end
protected :append
def post_checkout(*args)
old, new = args.shift, args.shift || 'HEAD'
if old == '0000000000000000000000000000000000000000'
old = EMPTY_DIR
elsif old.nil?
old = '@{-1}'
end
bundle(old, new, *args)
migrate(old, new, *args)
end
def bundle(old, new, *args)
return if args.first == '0'
return unless File.exist?('Gemfile')
if %x{git diff --name-only #{old} #{new}} =~ /^Gemfile|\.gemspec$/
begin
# If Bundler in turn spawns Git, it can get confused by $GIT_DIR
git_dir = ENV.delete('GIT_DIR')
%x{bundle check}
unless $?.success?
puts "Bundling..."
system("bundle | grep -v '^Using ' | grep -v ' is complete'")
end
ensure
ENV['GIT_DIR'] = git_dir
end
end
end
def migrate(old, new, *args)
return if args.first == '0'
schemas = %w(db/schema.rb db/development_structure.sql).select do |schema|
status = %x{git diff --name-status #{old} #{new} -- #{schema}}.chomp
system 'rake', 'db:create' if status =~ /^A/
status !~ /^D/ && !status.empty?
end
migrations = %x{git diff --name-status #{old} #{new} -- db/migrate}.scan(/.+/).map {|l| l.split(/\t/) }
begin
migrations.select {|(t,f)| %w(D M).include?(t)}.reverse.each do |type, file|
begin
system 'git', 'checkout', old, '--', file
unless system 'rake', 'db:migrate:down', "VERSION=#{File.basename(file)}"
raise Error, "Failed to rollback #{File.basename(file)}. Consider rake db:setup"
end
ensure
if type == 'D'
system 'git', 'rm', '--force', '--quiet', '--', file
else
system 'git', 'checkout', new, '--', file
end
end
end
if migrations.any? {|(t,f)| %w(A M).include?(t)}
system 'rake', 'db:migrate'
end
ensure
system 'git', 'checkout', '--', *schemas if schemas.any?
end
end
def resolve_schema(a, o, b, marker_size = 7)
system 'git', 'merge-file', "--marker-size=#{marker_size}", a, o, b
body = File.read(a)
asd = "ActiveRecord::Schema.define"
x = body.sub!(/^<+ .*\n#{asd}\(:version => (\d+)\) do\n=+\n#{asd}\(:version => (\d+)\) do\n>+ .*/) do
"#{asd}(:version => #{[$1, $2].max}) do"
end
File.open(a, 'w') { |f| f.write(body) }
if body.include?('<' * marker_size.to_i)
raise Failure, 'Failed to automatically resolve schema conflict'
end
end
end