xmigra 1.0.1 → 1.1.0
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 +4 -4
- data/lib/xmigra/access_artifact.rb +44 -0
- data/lib/xmigra/access_artifact_collection.rb +50 -0
- data/lib/xmigra/branch_upgrade.rb +62 -0
- data/lib/xmigra/db_support/mssql.rb +1070 -0
- data/lib/xmigra/function.rb +17 -0
- data/lib/xmigra/index.rb +21 -0
- data/lib/xmigra/index_collection.rb +38 -0
- data/lib/xmigra/migration.rb +27 -0
- data/lib/xmigra/migration_chain.rb +63 -0
- data/lib/xmigra/migration_conflict.rb +68 -0
- data/lib/xmigra/new_file.rb +4 -0
- data/lib/xmigra/new_migration_adder.rb +74 -0
- data/lib/xmigra/permission_script_writer.rb +67 -0
- data/lib/xmigra/program.rb +927 -0
- data/lib/xmigra/schema_manipulator.rb +39 -0
- data/lib/xmigra/schema_updater.rb +183 -0
- data/lib/xmigra/stored_procedure.rb +20 -0
- data/lib/xmigra/vcs_support/git.rb +275 -0
- data/lib/xmigra/vcs_support/svn.rb +213 -0
- data/lib/xmigra/version.rb +1 -1
- data/lib/xmigra/view.rb +17 -0
- data/lib/xmigra.rb +47 -3222
- data/test/runner.rb +53 -4
- metadata +22 -2
@@ -0,0 +1,213 @@
|
|
1
|
+
|
2
|
+
module XMigra
|
3
|
+
module SubversionSpecifics
|
4
|
+
VersionControlSupportModules << self
|
5
|
+
|
6
|
+
PRODUCTION_PATH_PROPERTY = 'xmigra:production-path'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def manages(path)
|
10
|
+
begin
|
11
|
+
return true if File.directory?(File.join(path, '.svn'))
|
12
|
+
rescue TypeError
|
13
|
+
return false
|
14
|
+
end
|
15
|
+
|
16
|
+
`svn info "#{path}" 2>&1`
|
17
|
+
return $?.success?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Run the svn command line client in XML mode and return a REXML::Document
|
21
|
+
def run_svn(subcmd, *args)
|
22
|
+
options = (Hash === args[-1]) ? args.pop : {}
|
23
|
+
no_result = !options.fetch(:get_result, true)
|
24
|
+
raw_result = options.fetch(:raw, false)
|
25
|
+
|
26
|
+
cmd_parts = ["svn", subcmd.to_s]
|
27
|
+
cmd_parts << "--xml" unless no_result || raw_result
|
28
|
+
cmd_parts.concat(
|
29
|
+
args.collect {|a| '""'.insert(1, a)}
|
30
|
+
)
|
31
|
+
cmd_str = cmd_parts.join(' ')
|
32
|
+
|
33
|
+
output = `#{cmd_str}`
|
34
|
+
raise(VersionControlError, "Subversion command failed with exit code #{$?.exitstatus}") unless $?.success?
|
35
|
+
return output if raw_result && !no_result
|
36
|
+
return REXML::Document.new(output) unless no_result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def subversion(*args)
|
41
|
+
SubversionSpecifics.run_svn(*args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_working_copy!
|
45
|
+
return unless production
|
46
|
+
|
47
|
+
schema_info = subversion_info
|
48
|
+
file_paths = Array.from_generator(method(:each_file_path))
|
49
|
+
status = subversion(:status, '--no-ignore', path)
|
50
|
+
unversioned_files = status.elements.each("status/target/entry/@path")
|
51
|
+
unversioned_files = unversioned_files.collect {|a| File.expand_path(a.to_s)}
|
52
|
+
|
53
|
+
unless (file_paths & unversioned_files).empty?
|
54
|
+
raise VersionControlError, "Some source files are not versions found in the repository"
|
55
|
+
end
|
56
|
+
status = nil
|
57
|
+
|
58
|
+
wc_rev = {}
|
59
|
+
working_rev = schema_info.elements["info/entry/@revision"].value.to_i
|
60
|
+
file_paths.each do |fp|
|
61
|
+
fp_info = subversion(:info, fp)
|
62
|
+
wc_rev[fp] = fp_wc_rev = fp_info.elements["info/entry/@revision"].value.to_i
|
63
|
+
if working_rev != fp_wc_rev
|
64
|
+
raise VersionControlError, "The working copy contains objects at multiple revisions"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
migrations.each do |m|
|
69
|
+
fpath = m.file_path
|
70
|
+
|
71
|
+
log = subversion(:log, "-r#{wc_rev[fpath]}:1", "--stop-on-copy", fpath)
|
72
|
+
if log.elements["log/logentry[2]"]
|
73
|
+
raise VersionControlError, "'#{fpath}' has been modified in the repository since it was created or copied"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Since a production script was requested, warn if we are not generating
|
78
|
+
# from a production branch
|
79
|
+
if branch_use != :production and self.respond_to? :warning
|
80
|
+
self.warning(<<END_OF_MESSAGE)
|
81
|
+
The branch backing the target working copy is not marked as a production branch.
|
82
|
+
END_OF_MESSAGE
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def vcs_information
|
87
|
+
info = subversion_info
|
88
|
+
return [
|
89
|
+
"Repository URL: #{info.elements["info/entry/url"].text}",
|
90
|
+
"Revision: #{info.elements["info/entry/@revision"].value}"
|
91
|
+
].join("\n")
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_conflict_info
|
95
|
+
# Check if the structure head is conflicted
|
96
|
+
structure_dir = Pathname.new(self.path) + SchemaManipulator::STRUCTURE_SUBDIR
|
97
|
+
status = subversion(:status, structure_dir + MigrationChain::HEAD_FILE)
|
98
|
+
return nil if status.elements["status/target/entry/wc-status/@item"].value != "conflicted"
|
99
|
+
|
100
|
+
chain_head = lambda do |extension|
|
101
|
+
pattern = MigrationChain::HEAD_FILE + extension
|
102
|
+
if extension.include? '*'
|
103
|
+
files = structure_dir.glob(MigrationChain::HEAD_FILE + extension)
|
104
|
+
raise VersionControlError, "Multiple #{pattern} files in structure directory" if files.length > 1
|
105
|
+
raise VersionControlError, "#{pattern} file missing from structure directory" if files.length < 1
|
106
|
+
else
|
107
|
+
files = [structure_dir.join(pattern)]
|
108
|
+
end
|
109
|
+
|
110
|
+
# Using YAML.parse_file and YAML::Syck::Node#transform rerenders
|
111
|
+
# scalars in the same style they were read from the source file:
|
112
|
+
return YAML.parse_file(files[0]).transform
|
113
|
+
end
|
114
|
+
|
115
|
+
if (structure_dir + (MigrationChain::HEAD_FILE + ".working")).exist?
|
116
|
+
# This is a merge conflict
|
117
|
+
|
118
|
+
# structure/head.yaml.working is from the current branch
|
119
|
+
# structure/head.yaml.merge-left.r* is the branch point
|
120
|
+
# structure/head.yaml.merge-right.r* is from the merged-in branch
|
121
|
+
this_head = chain_head.call(".working")
|
122
|
+
other_head = chain_head.call(".merge-right.r*")
|
123
|
+
branch_point = chain_head.call(".merge-left.r*")[MigrationChain::LATEST_CHANGE]
|
124
|
+
|
125
|
+
conflict = MigrationConflict.new(structure_dir, branch_point, [other_head, this_head])
|
126
|
+
|
127
|
+
branch_use {|u| conflict.branch_use = u}
|
128
|
+
else
|
129
|
+
# This is an update conflict
|
130
|
+
|
131
|
+
# structure/head.yaml.mine is from the working copy
|
132
|
+
# structure/head.yaml.r<lower> is the common ancestor
|
133
|
+
# structure/head.yaml.r<higher> is the newer revision
|
134
|
+
working_head = chain_head.call('.mine')
|
135
|
+
oldrev, newrev = nil, 0
|
136
|
+
structure_dir.glob(MigrationChain::HEAD_FILE + '.r*') do |fn|
|
137
|
+
if fn.to_s =~ /.r(\d+)$/
|
138
|
+
rev = $1.to_i
|
139
|
+
if oldrev.nil? or rev < oldrev
|
140
|
+
oldrev = rev
|
141
|
+
end
|
142
|
+
if newrev < rev
|
143
|
+
newrev = rev
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
repo_head = chain_head.call(".r#{newrev}")
|
148
|
+
branch_point = chain_head.call(".r#{oldrev}")[MigrationChain::LATEST_CHANGE]
|
149
|
+
|
150
|
+
conflict = MigrationConflict.new(structure_dir, branch_point, [repo_head, working_head])
|
151
|
+
branch_use {|u| conflict.branch_use = u}
|
152
|
+
|
153
|
+
fix_target, = conflict.migration_tweak
|
154
|
+
fix_target_st = subversion(:status, fix_target)
|
155
|
+
if fix_target_st.elements['status/target/entry/wc-status/@item'].value == 'modified'
|
156
|
+
conflict.scope = :working_copy
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
tool = self
|
161
|
+
conflict.after_fix = proc {tool.resolve_conflict!(structure_dir + MigrationChain::HEAD_FILE)}
|
162
|
+
|
163
|
+
return conflict
|
164
|
+
end
|
165
|
+
|
166
|
+
def branch_use
|
167
|
+
# Look for xmigra:production-path on the database directory (self.path)
|
168
|
+
return nil unless prod_path_element = subversion(:propget, PRODUCTION_PATH_PROPERTY, self.path).elements['properties/target/property']
|
169
|
+
|
170
|
+
prod_path_pattern = Regexp.new(prod_path_element.text)
|
171
|
+
|
172
|
+
use = prod_path_pattern.match(branch_identifier) ? :production : :development
|
173
|
+
if block_given?
|
174
|
+
yield use
|
175
|
+
else
|
176
|
+
return use
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def branch_identifier
|
181
|
+
return @subversion_branch_id if defined? @subversion_branch_id
|
182
|
+
dir_info = subversion_info
|
183
|
+
return @subversion_branch_id = dir_info.elements['info/entry/url'].text[
|
184
|
+
dir_info.elements['info/entry/repository/root'].text.length..-1
|
185
|
+
]
|
186
|
+
end
|
187
|
+
|
188
|
+
def production_pattern
|
189
|
+
subversion(:propget, PRODUCTION_PATH_PROPERTY, self.path, :raw=>true)
|
190
|
+
end
|
191
|
+
def production_pattern=(pattern)
|
192
|
+
subversion(:propset, PRODUCTION_PATH_PROPERTY, pattern, self.path, :get_result=>false)
|
193
|
+
end
|
194
|
+
|
195
|
+
def resolve_conflict!(path)
|
196
|
+
subversion(:resolve, '--accept=working', path, :get_result=>false)
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
def vcs_move(old_path, new_path)
|
201
|
+
subversion(:move, old_path, new_path, :get_result=>false)
|
202
|
+
end
|
203
|
+
|
204
|
+
def vcs_remove(path)
|
205
|
+
subversion(:remove, path, :get_result=>false)
|
206
|
+
end
|
207
|
+
|
208
|
+
def subversion_info
|
209
|
+
return @subversion_info if defined? @subversion_info
|
210
|
+
return @subversion_info = subversion(:info, self.path)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
data/lib/xmigra/version.rb
CHANGED
data/lib/xmigra/view.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
require 'xmigra/access_artifact'
|
3
|
+
|
4
|
+
module XMigra
|
5
|
+
class View < AccessArtifact
|
6
|
+
OBJECT_TYPE = "VIEW"
|
7
|
+
|
8
|
+
# Construct with a hash (as if loaded from a view YAML file)
|
9
|
+
def initialize(view_info)
|
10
|
+
@name = view_info["name"].dup.freeze
|
11
|
+
@depends_on = view_info.fetch("referencing", []).dup.freeze
|
12
|
+
@definition = view_info["sql"].dup.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :name, :depends_on
|
16
|
+
end
|
17
|
+
end
|