xmigra 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.html +232 -0
- data/README.md +113 -0
- data/Rakefile +6 -0
- data/bin/xmigra +5 -0
- data/lib/xmigra/version.rb +3 -0
- data/lib/xmigra.rb +3500 -0
- data/test/git_vcs.rb +242 -0
- data/test/runner.rb +129 -0
- data/xmigra.gemspec +28 -0
- metadata +90 -0
data/test/git_vcs.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
|
4
|
+
GIT_PRESENT = begin
|
5
|
+
`git --version` && $?.success?
|
6
|
+
rescue
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize_git_repo(dir)
|
11
|
+
Dir.chdir(dir) do
|
12
|
+
do_or_die "git init", "Failed to initialize git repository"
|
13
|
+
initialize_xmigra_schema
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def commit_all(msg)
|
18
|
+
do_or_die "git add -A"
|
19
|
+
do_or_die "git commit -m \"#{msg}\""
|
20
|
+
end
|
21
|
+
|
22
|
+
def commit_a_migration(desc_tail)
|
23
|
+
XMigra::NewMigrationAdder.new('.').tap do |tool|
|
24
|
+
tool.add_migration "Create #{desc_tail}"
|
25
|
+
end
|
26
|
+
commit_all "Added #{desc_tail}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_migration_chain_head
|
30
|
+
(Pathname('.') + XMigra::SchemaManipulator::STRUCTURE_SUBDIR + XMigra::MigrationChain::HEAD_FILE).open do |f|
|
31
|
+
YAML.load(f)[XMigra::MigrationChain::LATEST_CHANGE]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def make_this_branch_master
|
36
|
+
open('.gitattributes', 'w') do |f|
|
37
|
+
f.puts("%s %s=file://%s#%s" % [
|
38
|
+
XMigra::SchemaManipulator::DBINFO_FILE,
|
39
|
+
XMigra::GitSpecifics::MASTER_HEAD_ATTRIBUTE,
|
40
|
+
Dir.pwd,
|
41
|
+
`git rev-parse --abbrev-ref HEAD`.chomp
|
42
|
+
])
|
43
|
+
end
|
44
|
+
commit_all "Updated master database location"
|
45
|
+
end
|
46
|
+
|
47
|
+
if GIT_PRESENT
|
48
|
+
run_test "Initialize a git repository" do
|
49
|
+
1.temp_dirs do |repo|
|
50
|
+
initialize_git_repo(repo)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
run_test "XMigra recognizes git version control" do
|
55
|
+
1.temp_dirs do |repo|
|
56
|
+
initialize_git_repo(repo)
|
57
|
+
|
58
|
+
Dir.chdir(repo) do
|
59
|
+
tool = XMigra::SchemaManipulator.new('.')
|
60
|
+
assert {tool.is_a? XMigra::MSSQLSpecifics}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
run_test "XMigra can create a new migration" do
|
66
|
+
1.temp_dirs do |repo|
|
67
|
+
initialize_git_repo(repo)
|
68
|
+
|
69
|
+
Dir.chdir(repo) do
|
70
|
+
tool = XMigra::NewMigrationAdder.new('.')
|
71
|
+
description = "Create the first table"
|
72
|
+
fpath = tool.add_migration(description)
|
73
|
+
assert_include fpath.to_s, description
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
run_test "XMigra can recognize a migration chain conflict" do
|
79
|
+
1.temp_dirs do |repo|
|
80
|
+
initialize_git_repo(repo)
|
81
|
+
|
82
|
+
Dir.chdir(repo) do
|
83
|
+
commit_a_migration "first table"
|
84
|
+
do_or_die "git branch side"
|
85
|
+
commit_a_migration "foo table"
|
86
|
+
do_or_die "git checkout side 2>/dev/null"
|
87
|
+
commit_a_migration "bar table"
|
88
|
+
`git merge master` # This is not going to go well!
|
89
|
+
|
90
|
+
XMigra::SchemaManipulator.new('.').tap do |tool|
|
91
|
+
conflict = tool.get_conflict_info
|
92
|
+
assert {not conflict.nil?}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
run_test "XMigra can fix a migration chain conflict" do
|
99
|
+
1.temp_dirs do |repo|
|
100
|
+
initialize_git_repo(repo)
|
101
|
+
|
102
|
+
Dir.chdir(repo) do
|
103
|
+
commit_a_migration "first table"
|
104
|
+
do_or_die "git branch side"
|
105
|
+
commit_a_migration "foo table"
|
106
|
+
do_or_die "git checkout side 2>/dev/null"
|
107
|
+
commit_a_migration "bar table"
|
108
|
+
my_head = get_migration_chain_head
|
109
|
+
`git merge master` # This is not going to go well!
|
110
|
+
|
111
|
+
XMigra::SchemaManipulator.new('.').tap do |tool|
|
112
|
+
conflict = tool.get_conflict_info
|
113
|
+
assert {not conflict.nil?}
|
114
|
+
conflict.fix_conflict!
|
115
|
+
|
116
|
+
# Check that the merged migrations are at the end of the chain
|
117
|
+
assert_neq get_migration_chain_head, my_head
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
run_test "XMigra is aware of upstream branch relationship" do
|
124
|
+
2.temp_dirs do |upstream, repo|
|
125
|
+
initialize_git_repo(upstream)
|
126
|
+
|
127
|
+
Dir.chdir(upstream) do
|
128
|
+
commit_a_migration "first table"
|
129
|
+
end
|
130
|
+
|
131
|
+
`git clone "#{upstream}" "#{repo}" 2>/dev/null`
|
132
|
+
|
133
|
+
Dir.chdir(upstream) do
|
134
|
+
commit_a_migration "foo table"
|
135
|
+
end
|
136
|
+
|
137
|
+
Dir.chdir(repo) do
|
138
|
+
commit_a_migration "bar table"
|
139
|
+
|
140
|
+
# Get head migration
|
141
|
+
downstream_head = get_migration_chain_head
|
142
|
+
|
143
|
+
`git pull origin master 2>/dev/null`
|
144
|
+
|
145
|
+
XMigra::SchemaManipulator.new('.').tap do |tool|
|
146
|
+
conflict = tool.get_conflict_info
|
147
|
+
assert {conflict}
|
148
|
+
conflict.fix_conflict!
|
149
|
+
|
150
|
+
# Check that head migration is still what it was (i.e.
|
151
|
+
# merged migrations precede local branch migrations, preventing
|
152
|
+
# modifications to upstream)
|
153
|
+
assert_eq get_migration_chain_head, downstream_head
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
run_test "XMigra will not generate a production script from a working tree that does not match the master branch" do
|
160
|
+
2.temp_dirs do |upstream, repo|
|
161
|
+
initialize_git_repo(upstream)
|
162
|
+
|
163
|
+
Dir.chdir(upstream) do
|
164
|
+
commit_a_migration "first table"
|
165
|
+
make_this_branch_master
|
166
|
+
end
|
167
|
+
|
168
|
+
`git clone "#{upstream.expand_path}" "#{repo}" 2>/dev/null`
|
169
|
+
|
170
|
+
Dir.chdir(upstream) do
|
171
|
+
commit_a_migration "foo table"
|
172
|
+
end
|
173
|
+
|
174
|
+
Dir.chdir(repo) do
|
175
|
+
commit_a_migration "bar table"
|
176
|
+
|
177
|
+
XMigra::SchemaUpdater.new('.').tap do |tool|
|
178
|
+
tool.production = true
|
179
|
+
assert_neq(tool.branch_use, :production)
|
180
|
+
assert_raises(XMigra::VersionControlError) {tool.update_sql}
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
run_test "XMigra will generate a production script from an older commit from the master branch" do
|
187
|
+
2.temp_dirs do |upstream, repo|
|
188
|
+
initialize_git_repo(upstream)
|
189
|
+
|
190
|
+
Dir.chdir(upstream) do
|
191
|
+
commit_a_migration "first table"
|
192
|
+
make_this_branch_master
|
193
|
+
end
|
194
|
+
|
195
|
+
`git clone "#{upstream.expand_path}" "#{repo}" 2>/dev/null`
|
196
|
+
|
197
|
+
Dir.chdir(upstream) do
|
198
|
+
commit_a_migration "foo table"
|
199
|
+
end
|
200
|
+
|
201
|
+
Dir.chdir(repo) do
|
202
|
+
XMigra::SchemaUpdater.new('.').tap do |tool|
|
203
|
+
tool.production = true
|
204
|
+
assert_noraises {tool.update_sql}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
run_test "XMigra will generate a production script even if an access object's definition changes" do
|
211
|
+
2.temp_dirs do |upstream, repo|
|
212
|
+
initialize_git_repo(upstream)
|
213
|
+
|
214
|
+
Dir.chdir(upstream) do
|
215
|
+
commit_a_migration "first table"
|
216
|
+
make_this_branch_master
|
217
|
+
Dir.mkdir(XMigra::SchemaManipulator::ACCESS_SUBDIR)
|
218
|
+
(Pathname(XMigra::SchemaManipulator::ACCESS_SUBDIR) + 'Bar.yaml').open('w') do |f|
|
219
|
+
YAML.dump({'define'=>'stored procedure', 'sql'=>'definition goes here'}, f)
|
220
|
+
end
|
221
|
+
commit_all 'Defined Bar'
|
222
|
+
(Pathname(XMigra::SchemaManipulator::ACCESS_SUBDIR) + 'Bar.yaml').open('w') do |f|
|
223
|
+
YAML.dump({'define'=>'view', 'sql'=>'definition goes here'}, f)
|
224
|
+
end
|
225
|
+
commit_all 'Changed Bar to a view'
|
226
|
+
end
|
227
|
+
|
228
|
+
`git clone "#{upstream.expand_path}" "#{repo}" 2>/dev/null`
|
229
|
+
|
230
|
+
Dir.chdir(upstream) do
|
231
|
+
commit_a_migration "foo table"
|
232
|
+
end
|
233
|
+
|
234
|
+
Dir.chdir(repo) do
|
235
|
+
XMigra::SchemaUpdater.new('.').tap do |tool|
|
236
|
+
tool.production = true
|
237
|
+
assert_noraises {tool.update_sql}
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
data/test/runner.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'fileutils'
|
3
|
+
require 'pathname'
|
4
|
+
require 'stringio'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
TESTS = %w[
|
8
|
+
git_vcs
|
9
|
+
]
|
10
|
+
|
11
|
+
$:.unshift Pathname(__FILE__).expand_path.dirname.dirname
|
12
|
+
require 'xmigra'
|
13
|
+
|
14
|
+
$test_count = 0
|
15
|
+
$test_successes = 0
|
16
|
+
$tests_failed = []
|
17
|
+
|
18
|
+
$xmigra_test_system = 'Microsoft SQL Server'
|
19
|
+
|
20
|
+
def run_test(name, &block)
|
21
|
+
$test_count += 1
|
22
|
+
|
23
|
+
if child_pid = Process.fork
|
24
|
+
Process.wait(child_pid)
|
25
|
+
|
26
|
+
if $?.success?
|
27
|
+
print '.'
|
28
|
+
$test_successes += 1
|
29
|
+
else
|
30
|
+
print 'F'
|
31
|
+
$tests_failed << name
|
32
|
+
end
|
33
|
+
else
|
34
|
+
begin
|
35
|
+
block.call
|
36
|
+
exit! 0
|
37
|
+
rescue
|
38
|
+
puts
|
39
|
+
puts "Exception: #{$!}"
|
40
|
+
puts $!.backtrace
|
41
|
+
exit! 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Integer
|
47
|
+
def temp_dirs(prefix='')
|
48
|
+
tmpdirs = []
|
49
|
+
begin
|
50
|
+
(1..self).each do |i|
|
51
|
+
tmpdirs << Pathname(Dir.mktmpdir([prefix, ".#{i}"]))
|
52
|
+
end
|
53
|
+
|
54
|
+
yield(*tmpdirs)
|
55
|
+
ensure
|
56
|
+
tmpdirs.each do |dp|
|
57
|
+
begin
|
58
|
+
FileUtils.remove_entry dp
|
59
|
+
rescue
|
60
|
+
# Skip failure
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def do_or_die(command, message=nil, exc_type=Exception)
|
68
|
+
output = `#{command}`
|
69
|
+
$?.success? || raise(exc_type, message || ("Unable to " + command + "\n" + output))
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize_xmigra_schema(path='.', options={})
|
73
|
+
(Pathname(path) + XMigra::SchemaManipulator::DBINFO_FILE).open('w') do |f|
|
74
|
+
YAML.dump({
|
75
|
+
'system' => $xmigra_test_system,
|
76
|
+
}.merge(options[:db_info] || {}), f)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class AssertionFailure < Exception; end
|
81
|
+
|
82
|
+
def assert(message=nil, &block)
|
83
|
+
get_message = proc {
|
84
|
+
if !message and File.exist?(block.source_location[0])
|
85
|
+
File.read(block.source_location[0]).lines[block.source_location[1] - 1].strip
|
86
|
+
elsif message.is_a? Proc
|
87
|
+
message.call
|
88
|
+
else
|
89
|
+
message
|
90
|
+
end
|
91
|
+
}
|
92
|
+
|
93
|
+
block.call || raise(AssertionFailure, get_message.call)
|
94
|
+
end
|
95
|
+
|
96
|
+
def assert_eq(actual, expected)
|
97
|
+
assert(proc {"#{actual.inspect} was not equal to #{expected.inspect}"}) {actual == expected}
|
98
|
+
end
|
99
|
+
|
100
|
+
def assert_neq(actual, expected)
|
101
|
+
assert(proc {"Value #{actual.inspect} was unexpected"}) {actual != expected}
|
102
|
+
end
|
103
|
+
|
104
|
+
def assert_include(container, item)
|
105
|
+
assert(proc {"#{item.inspect} was not in #{container.inspect}"}) {container.include? item}
|
106
|
+
end
|
107
|
+
|
108
|
+
def assert_raises(expected_exception)
|
109
|
+
begin
|
110
|
+
yield
|
111
|
+
rescue Exception => ex
|
112
|
+
assert("#{ex.class} is not #{expected_exception}") {ex.is_a? expected_exception}
|
113
|
+
return
|
114
|
+
end
|
115
|
+
|
116
|
+
assert("No #{expected_exception} raised") {false}
|
117
|
+
end
|
118
|
+
|
119
|
+
def assert_noraises
|
120
|
+
yield
|
121
|
+
end
|
122
|
+
|
123
|
+
TESTS.each {|t| require "test/#{t}"}
|
124
|
+
|
125
|
+
puts
|
126
|
+
puts "#{$test_successes}/#{$test_count} succeeded"
|
127
|
+
puts "Failed tests:" unless $tests_failed.empty?
|
128
|
+
$tests_failed.each {|name| puts " #{name}"}
|
129
|
+
|
data/xmigra.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'xmigra/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "xmigra"
|
8
|
+
spec.version = XMigra::VERSION
|
9
|
+
spec.authors = ["Next IT Corporation", "Richard Weeks"]
|
10
|
+
spec.email = ["rtweeks21@gmail.com"]
|
11
|
+
spec.summary = %q{Toolkit for managing database schema evolution with version control.}
|
12
|
+
spec.description = <<-END
|
13
|
+
XMigra is a suite of tools for managing database schema evolution with
|
14
|
+
version controlled files. All database manipulations are written in
|
15
|
+
SQL (specific to the target database). Works with Git or Subversion.
|
16
|
+
Currently supports Microsoft SQL Server.
|
17
|
+
END
|
18
|
+
spec.homepage = "https://github.com/rtweeks/xmigra"
|
19
|
+
spec.license = "CC-BY-SA 4.0 Itnl."
|
20
|
+
|
21
|
+
spec.files = `git ls-files -z`.split("\x0")
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.test_files = ["test/runner.rb"]
|
24
|
+
spec.require_paths = ["lib"]
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xmigra
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Next IT Corporation
|
8
|
+
- Richard Weeks
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.5'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.5'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
description: |2
|
43
|
+
XMigra is a suite of tools for managing database schema evolution with
|
44
|
+
version controlled files. All database manipulations are written in
|
45
|
+
SQL (specific to the target database). Works with Git or Subversion.
|
46
|
+
Currently supports Microsoft SQL Server.
|
47
|
+
email:
|
48
|
+
- rtweeks21@gmail.com
|
49
|
+
executables:
|
50
|
+
- xmigra
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- ".gitignore"
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE.html
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- bin/xmigra
|
60
|
+
- lib/xmigra.rb
|
61
|
+
- lib/xmigra/version.rb
|
62
|
+
- test/git_vcs.rb
|
63
|
+
- test/runner.rb
|
64
|
+
- xmigra.gemspec
|
65
|
+
homepage: https://github.com/rtweeks/xmigra
|
66
|
+
licenses:
|
67
|
+
- CC-BY-SA 4.0 Itnl.
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.2.1
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Toolkit for managing database schema evolution with version control.
|
89
|
+
test_files:
|
90
|
+
- test/runner.rb
|