xmigra 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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