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.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module XMigra
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -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