prick 0.2.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -5
  3. data/Gemfile +4 -1
  4. data/TODO +10 -0
  5. data/doc/prick.txt +114 -0
  6. data/exe/prick +328 -402
  7. data/lib/ext/fileutils.rb +18 -0
  8. data/lib/ext/forward_method.rb +18 -0
  9. data/lib/ext/shortest_path.rb +44 -0
  10. data/lib/prick.rb +20 -10
  11. data/lib/prick/branch.rb +254 -0
  12. data/lib/prick/builder.rb +164 -0
  13. data/lib/prick/cache.rb +34 -0
  14. data/lib/prick/command.rb +19 -11
  15. data/lib/prick/constants.rb +122 -48
  16. data/lib/prick/database.rb +28 -20
  17. data/lib/prick/diff.rb +125 -0
  18. data/lib/prick/exceptions.rb +15 -3
  19. data/lib/prick/git.rb +77 -30
  20. data/lib/prick/head.rb +183 -0
  21. data/lib/prick/migration.rb +40 -200
  22. data/lib/prick/program.rb +493 -0
  23. data/lib/prick/project.rb +523 -351
  24. data/lib/prick/rdbms.rb +4 -13
  25. data/lib/prick/schema.rb +16 -90
  26. data/lib/prick/share.rb +64 -0
  27. data/lib/prick/state.rb +192 -0
  28. data/lib/prick/version.rb +62 -29
  29. data/libexec/strip-comments +33 -0
  30. data/make_releases +48 -345
  31. data/make_schema +10 -0
  32. data/prick.gemspec +14 -23
  33. data/share/diff/diff.after-tables.sql +4 -0
  34. data/share/diff/diff.before-tables.sql +4 -0
  35. data/share/diff/diff.tables.sql +8 -0
  36. data/share/migration/diff.tables.sql +8 -0
  37. data/share/migration/features.yml +6 -0
  38. data/share/migration/migrate.sql +3 -0
  39. data/share/migration/migrate.yml +8 -0
  40. data/share/migration/tables.sql +3 -0
  41. data/share/schema/build.yml +14 -0
  42. data/share/schema/schema.sql +5 -0
  43. data/share/schema/schema/build.yml +3 -0
  44. data/share/schema/schema/prick/build.yml +14 -0
  45. data/share/schema/schema/prick/data.sql +7 -0
  46. data/share/schema/schema/prick/schema.sql +5 -0
  47. data/share/{schemas/prick/schema.sql → schema/schema/prick/tables.sql} +2 -5
  48. data/{file → share/schema/schema/public/.keep} +0 -0
  49. data/share/schema/schema/public/build.yml +14 -0
  50. data/share/schema/schema/public/schema.sql +3 -0
  51. data/test_assorted +192 -0
  52. data/test_feature +112 -0
  53. data/test_refactor +34 -0
  54. data/test_single_dev +83 -0
  55. metadata +43 -68
  56. data/lib/prick/build.rb +0 -376
  57. data/lib/prick/migra.rb +0 -22
  58. data/share/schemas/prick/data.sql +0 -8
@@ -2,12 +2,24 @@
2
2
  module Prick
3
3
  class Error < RuntimeError; end
4
4
  class Fail < Error; end
5
+
5
6
  class NotYet < NotImplementedError
6
- def initialize() super("Program error - Not yet implemented") end
7
+ def initialize() super("Internal error: Not yet implemented") end
8
+ end
9
+
10
+ class NotThis < ScriptError
11
+ def initialize() super("Internal error: Abstract method called") end
7
12
  end
8
- class AbstractMethod < ScriptError
9
- def initialize() super("Program error - Abstract method called") end
13
+
14
+ class Abstract < ScriptError
15
+ def initialize() super("Internal error: Abstract method called") end
10
16
  end
17
+
18
+ AbstractMethod = Abstract
19
+
11
20
  class Internal < ScriptError; end
21
+ class Oops < Internal
22
+ def initialize() super("Oops, this shouldn't happen") end
23
+ end
12
24
  end
13
25
 
data/lib/prick/git.rb CHANGED
@@ -9,34 +9,41 @@ module Prick
9
9
  Command.command "git init"
10
10
  end
11
11
 
12
- # Returns true if the repository has no modified files. Requires the
13
- # repository to have at least one commit. Creating the repository using
14
- # Project::initialize_directory guarantees that
12
+ # Returns true if the repository has no modified files or unresolved
13
+ # conflicts. Requires the repository to have at least one commit. Creating
14
+ # the repository using Project::initialize_directory guarantees that
15
15
  def self.clean?()
16
16
  Command.command("git status").find { |l|
17
- l =~ /Changes to be committed:/ || l =~ /^\s*modified:/
17
+ (l =~ /^Changes to be committed:/) ||
18
+ (l =~ /^Unmerged paths:/) ||
19
+ (l =~ /^Changes not staged for commit:/)
18
20
  }.nil?
19
21
  end
20
22
 
21
23
  # Returns true if the repository is on a detached branch
22
24
  def self.detached?
23
- line = File.readlines(".git/HEAD").first.chomp
24
- line =~ /^ref:/ ? false : true
25
+ Command.command? "git symbolic-ref -q HEAD", expect: 1
25
26
  end
26
27
 
27
28
  # The current tag. This is only defined if the repository is in "detached
28
29
  # head" mode
29
- def self.current_tag() raise "Not yet implemented" end
30
-
30
+ def self.current_tag()
31
+ self.detached? ? Command.command("git describe --tags").first.sub(/^v/, "") : nil
32
+ end
33
+
31
34
  # Return true if `version` has an associated tag
32
35
  def self.tag?(version)
33
- !list_tags.grep(version).empty?
36
+ !list_tags.grep(version.to_s).empty?
34
37
  end
35
38
 
36
- # List tag versions
37
39
  # Create version tag
38
- def self.create_tag(version)
39
- Command.command "git tag -a 'v#{version}' -m 'Release #{version}'"
40
+ def self.create_tag(version, message: "Release #{version}", commit_id: nil)
41
+ Command.command "git tag -a 'v#{version}' -m '#{message}' #{commit_id}"
42
+ end
43
+
44
+ # Create a cancel-version tag
45
+ def self.cancel_tag(version)
46
+ create_tag("#{version}_cancelled", message: "Cancel #{version}", commit_id: tag_id(version))
40
47
  end
41
48
 
42
49
  def self.delete_tag(version, remote: false)
@@ -44,18 +51,30 @@ module Prick
44
51
  Command.command("git push --delete origin 'v#{version}'", fail: false) if remote
45
52
  end
46
53
 
54
+ def self.tag_id(version)
55
+ Command.command("git rev-parse 'v#{version}^{}'").first
56
+ end
57
+
47
58
  # Checkout a version tag as a detached head
48
59
  def self.checkout_tag(version)
49
60
  Command.command "git checkout 'v#{version}'"
50
61
  end
51
62
 
52
- def self.list_tags
53
- Command.command("git tag").map { |tag| tag.sub(/^v(#{VERSION_SUB_RE})/, '\1') }
63
+ def self.list_tags(include_cancelled: false)
64
+ tags = Command.command("git tag")
65
+ if !include_cancelled
66
+ cancelled = tags.select { |tag| tag =~ /_cancelled$/ }
67
+ for cancel_tag in cancelled
68
+ tags.delete(cancel_tag)
69
+ tags.delete(cancel_tag.sub(/_cancelled$/, ""))
70
+ end
71
+ end
72
+ tags.map { |tag| tag = tag[1..-1] }
54
73
  end
55
74
 
56
- # Name of the current branch
75
+ # Name of the current branch. This is nil if on a tag ("detached HEAD")
57
76
  def self.current_branch()
58
- Command.command("git rev-parse --abbrev-ref HEAD").first
77
+ self.detached? ? nil : Command.command("git rev-parse --abbrev-ref HEAD").first
59
78
  end
60
79
 
61
80
  # Check if branch exist
@@ -69,6 +88,11 @@ module Prick
69
88
  Command.command "git branch #{name}"
70
89
  end
71
90
 
91
+ # Rename a branch
92
+ def self.rename_branch(from, to)
93
+ Command.command "git branch -m #{from} #{to}"
94
+ end
95
+
72
96
  # Destroy branch
73
97
  def self.delete_branch(name)
74
98
  Command.command "git branch -D #{name}", fail: false
@@ -94,28 +118,46 @@ module Prick
94
118
 
95
119
  Command.command "git merge --no-commit #{name}", fail: false
96
120
 
97
- # Reinstate included files
98
- files.each { |path, content|
121
+ # Restore excluded files
122
+ files.each { |path, content|
99
123
  File.open(path, "w") { |file| file.puts(content) }
100
124
  # Resolve git unmerged status
101
125
  Git.add(path)
102
126
  }
127
+
128
+ # TODO Detect outstanding merges
129
+ end
130
+
131
+ def self.merge_tag(name, exclude_files: [], fail: false)
132
+ merge_branch(name, exclude_files: exclude_files, fail: fail)
103
133
  end
104
134
 
105
- # List branches
106
- def self.list_branches
107
- Command.command "git branch --format='%(refname:short)'"
135
+ # List branches. Detached head "branches" are ignored unless :detached_head is true
136
+ def self.list_branches(detached_head: false)
137
+ if detached_head
138
+ Command.command "git branch --format='%(refname:short)'"
139
+ else
140
+ Command.command "git for-each-ref --format='%(refname:short)' refs/heads/*"
141
+ end
108
142
  end
109
143
 
110
144
  # Add a file to the index of the current branch
111
145
  def self.add(*files)
112
- Array(files).each { |file|
146
+ Array(files).flatten.each { |file|
113
147
  Dir.chdir(File.dirname(file)) {
114
148
  Command.command "git add '#{File.basename(file)}'"
115
149
  }
116
150
  }
117
151
  end
118
152
 
153
+ def self.changed?(file)
154
+ Command.command("git status --porcelain").any? { |l| l =~ /^.M #{file}$/ }
155
+ end
156
+
157
+ def self.added?(file)
158
+ Command.command("git status --porcelain").any? { |l| l =~ /^A. #{file}$/ }
159
+ end
160
+
119
161
  # Return content of file in the given tag or branch. Defaults to HEAD
120
162
  def self.readlines(file, tag: nil, branch: nil)
121
163
  !(tag && branch) or raise Internal, "Can't use both tag: and branch: options"
@@ -127,7 +169,7 @@ module Prick
127
169
  end.map { |l| "#{l}\n" }
128
170
  end
129
171
 
130
- # Return content of file
172
+ # Return content of file as a String
131
173
  def self.read(file, tag: nil, branch: nil)
132
174
  !(tag && branch) or raise Internal, "Can't use both tag: and branch: options"
133
175
  if tag
@@ -135,18 +177,23 @@ module Prick
135
177
  else
136
178
  branch ||= "HEAD"
137
179
  Command.command "git show #{branch}:#{file}"
138
- end.join("\n")
180
+ end.join("\n") + "\n"
139
181
  end
140
182
 
141
- def self.rm(file)
142
- Dir.chdir(File.dirname(file)) {
143
- Command.command "git rm -f '#{File.basename(file)}'", fail: false
183
+ def self.rm(*files)
184
+ Array(files).flatten.each { |file|
185
+ Dir.chdir(File.dirname(file)) {
186
+ Command.command "git rm -f '#{File.basename(file)}'", fail: false
187
+ }
144
188
  }
145
189
  end
146
190
 
147
- def self.rm_rf(file)
148
- Dir.chdir(File.dirname(file)) {
149
- Command.command "git rm -rf '#{File.basename(file)}'", fail: false
191
+ def self.rm_rf(*files)
192
+ Array(files).flatten.each { |file|
193
+ Dir.chdir(File.dirname(file)) {
194
+ next if file == ".keep"
195
+ Command.command "git rm -rf '#{File.basename(file)}'", fail: false
196
+ }
150
197
  }
151
198
  end
152
199
 
data/lib/prick/head.rb ADDED
@@ -0,0 +1,183 @@
1
+
2
+ module Prick
3
+ class Head
4
+ def project_name() Prick.project.name end
5
+
6
+ # Usually equal to #version.to_s
7
+ attr_reader :name
8
+
9
+ # Version. This should be equal to schema.version
10
+ attr_reader :version
11
+
12
+ def base_version() @migration.base_version end
13
+
14
+ attr_reader :schema
15
+
16
+ attr_reader :migration
17
+
18
+ def clean?() Git::clean? end
19
+
20
+ def tag?() false end
21
+ def release_tag?() false end
22
+ def migration_tag?() false end
23
+
24
+ def branch?() false end
25
+ def release_branch?() false end
26
+ def prerelease_branch?() false end
27
+ def feature_branch?() false end
28
+ def migration_branch?() false end
29
+
30
+ def initialize(name, version, migration)
31
+ @name = name
32
+ @version = version
33
+ @schema = Schema.new
34
+ @migration = migration
35
+ end
36
+
37
+ # TODO: Handle migrations
38
+ def self.load(name)
39
+ version, tag = Version.parse(name)
40
+ if tag
41
+ ReleaseTag.load
42
+ else
43
+ if version.release?
44
+ ReleaseBranch.load
45
+ elsif version.prerelease?
46
+ PrereleaseBranch.load(version)
47
+ elsif version.feature?
48
+ FeatureBranch.load(version)
49
+ else
50
+ raise NotHere
51
+ end
52
+ end
53
+ end
54
+
55
+ def create()
56
+ raise NotThis
57
+ end
58
+
59
+ def build(database)
60
+ schema.build(database)
61
+ end
62
+
63
+ def self.database_name(version)
64
+ version.truncate(:pre).to_s
65
+ end
66
+ end
67
+
68
+ class Tag < Head
69
+ def tag?() true end
70
+
71
+ def initialize(version, base_version, migration = nil)
72
+ migration ||= Migration.new(version, base_version)
73
+ super(version.to_s, version, migration)
74
+ end
75
+
76
+ def self.load
77
+ state = Migration.load
78
+ self.new(state.version, state.base_version)
79
+ end
80
+
81
+ def create
82
+ initial_branch_name = "#{version}_initial"
83
+ clean? or raise Internal, "Repository is not clean"
84
+ !Git.detached? or raise Internal, "Not on a branch"
85
+ Git.create_branch(initial_branch_name)
86
+ Git.checkout_branch(initial_branch_name)
87
+ migration.update(version)
88
+ schema.version = version
89
+ Git.commit "Created release v#{version}"
90
+ Git.create_tag(version)
91
+ Git.checkout_tag(version)
92
+ self
93
+ end
94
+ end
95
+
96
+ class ReleaseTag < Tag
97
+ def release_tag?() true end
98
+ end
99
+
100
+ # TODO
101
+ class PrereleaseTag < Tag
102
+ end
103
+
104
+ class MigrationTag < Tag
105
+ def migration_tag?() true end
106
+ end
107
+
108
+ class Branch < Head
109
+ def branch?() true end
110
+
111
+ def initialize(name, version, migration)
112
+ super(name, version, migration)
113
+ end
114
+
115
+ def create()
116
+ Git.create_branch(version)
117
+ Git.checkout_branch(version)
118
+ migration.create
119
+ self
120
+ end
121
+ end
122
+
123
+ class ReleaseBranch < Branch
124
+ def release_branch?() true end
125
+
126
+ def initialize(fork = nil, base_version)
127
+ if fork
128
+ version = Version.new(base_version, fork: fork)
129
+ else
130
+ version = base_version
131
+ end
132
+ super(version.to_s, version, Migration.new(nil, base_version))
133
+ end
134
+
135
+ def self.load
136
+ state = MigrationState.load
137
+ ReleaseBranch.new(nil, state.base_version)
138
+ end
139
+ end
140
+
141
+ class PrereleaseBranch < Branch
142
+ def prerelease_branch?() true end
143
+
144
+ def initialize(version, base_version)
145
+ target_version = version.truncate(:pre)
146
+ super(version.to_s, target_version, Migration.new(target_version, base_version))
147
+ end
148
+
149
+ def self.load
150
+ state = MigrationState.load
151
+ PrereleaseBranch.new(state.version, state.base_version)
152
+ end
153
+ end
154
+
155
+ class FeatureBranch < Branch
156
+ def feature_branch?() true end
157
+
158
+ def initialize(feature_name, base_version)
159
+ name = Version.new(base_version, feature: feature_name).to_s
160
+ super(name, base_version, FeatureMigration.new(feature_name, base_version))
161
+ end
162
+
163
+ def self.load
164
+ state = MigrationState.load
165
+ FeatureBranch.new(state.version.feature)
166
+ end
167
+ end
168
+
169
+ # TODO: Versioned migrations (or maybe not)
170
+ class MigrationBranch < Branch
171
+ def migrationn_branch?() true end
172
+
173
+ def initialize(version, base_version)
174
+ if (version.fork || "") == (base_version.fork || "")
175
+ name = version.to_s + "-" + base_version.semver.to_s
176
+ else
177
+ name = version.to_s + "-" + base_version.to_s
178
+ end
179
+ super(name, version, Migration.new(version, base_version))
180
+ end
181
+ end
182
+ end
183
+
@@ -1,230 +1,70 @@
1
1
 
2
- require 'yaml'
3
-
4
2
  module Prick
5
3
  class Migration
6
- KEEP_FILE = ".keep"
7
-
8
- attr_reader :path
9
-
10
- def files() raise AbstractMethod end
11
- def release_files() files end
12
-
13
- # Return versions of the features in this migration
14
- def feature_versions() raise AbstractMethod end
15
-
16
- def initialize(path, template_dir)
17
- @path = path
18
- @template_dir = template_dir
19
- end
20
-
21
- def exist?() File.exist?(keep_file) end
22
- def create() FileUtils.touch_p(keep_file); Git.add(keep_file) end
23
- def destroy() Git.rm_rf(path) end
24
-
25
- def present?() exist? && files.all? { |f| File.exist?(f) } end
26
- def prepare() files.each { |f| FileUtils.cp(template_file(f), path) }; Git.add(path) end
27
-
28
- def include_feature(feature) raise AbstractMethod end
29
-
30
- def migrate() raise AbstractMethod end
31
-
32
- def to_s() path end
33
- def <=>(other) path <=> other.path end
34
-
35
- def self.path(version)
36
- if version.feature?
37
- File.join(FEATURE_DIR, version.truncate(:feature).to_s, version.feature)
38
- else
39
- File.join(FEATURE_DIR, version)
40
- end
41
- end
42
-
43
- def self.version(path)
44
- Version.new(path.delete_prefix("#{FEATURE_DIR}/").sub("/", "_"))
45
- end
46
-
47
- def self.feature?(path)
48
- Version.new(path.delete_prefix("#{FEATURE_DIR}/").sub("/", "_")).feature?
49
- end
50
-
51
- def self.files(path) raise AbstractMethod end
52
- def self.release_files(path) files(path) end
53
-
54
- def dump(&block)
55
- # puts self.class
56
- # indent {
57
- # puts "path : #{path}"
58
- # puts "files: #{files.inspect}"
59
- # puts "release_files: #{release_files.inspect}"
60
- # puts "feature_versions: #{feature_versions.map(&:to_s).inspect}"
61
- # puts "keep_file: #{keep_file}"
62
- # yield if block_given?
63
- # }
64
- end
65
-
66
- protected
67
- def template_file(path) File.join(SHARE_PATH, @template_dir, File.basename(path)) end
68
- def keep_file() File.join(path, KEEP_FILE) end
69
- end
70
-
71
- class ReleaseMigration < Migration
72
- FEATURES_TMPL_DIR = "features"
73
- FILES = [
74
- FEATURES_SQL = "features.sql",
75
- FEATURES_YML = "features.yml",
76
- MIGRATIONS_SQL = "migrations.sql",
77
- DIFF_SQL = "diff.sql"
78
- ]
79
-
80
- attr_reader :features_yml
81
- attr_reader :features_sql
82
- attr_reader :migrations_sql
83
- attr_reader :diff_sql
84
-
85
- def files() [features_yml, features_sql, migrations_sql, diff_sql] end
4
+ attr_reader :version
5
+ attr_reader :base_version
86
6
 
87
- def initialize(path)
88
- super(path, FEATURES_TMPL_DIR)
89
- @features_yml = File.join(path, FEATURES_YML)
90
- @features_sql = File.join(path, FEATURES_SQL)
91
- @migrations_sql = File.join(path, MIGRATIONS_SQL)
92
- @diff_sql = File.join(path, DIFF_SQL)
7
+ def initialize(version, base_version)
8
+ @version = version
9
+ @base_version = base_version
93
10
  end
94
11
 
95
- def feature_versions() read_features_yml.map { |path| Migration.version(path) } end
96
- def feature_paths() read_features_yml end
97
-
98
- # `feature` is a Feature object
99
- def include_feature(migration, append: true)
100
- migration.is_a?(FeatureMigration) or raise "Expected FeatureMigration object, got #{migration.class}"
101
- !feature_paths.include?(migration.path) or raise Error, "Feature #{migration.version} is already included"
102
- exclude_files = [Schema.data_file] +
103
- migration.release_files +
104
- migration.feature_paths.map { |path| FeatureMigration.release_files(path) }.flatten
105
-
106
- Git.merge_branch(migration.version, exclude_files: exclude_files)
107
-
108
- feature_paths = YAML.load(Git.read(migration.features_yml, branch: migration.version))
109
- append_features_yml(feature_paths, append: append)
110
-
111
- feature_paths.each { |feature_path|
112
- if path != File.dirname(feature_path)
113
- FileUtils.ln_s(File.join("../..", feature_path), path)
114
- Git.add(File.join(path, File.basename(feature_path)))
115
- end
116
- }
12
+ def self.load
13
+ state = MigrationState.load
14
+ Migration.new(state.version, state.base_version)
117
15
  end
118
16
 
119
- def migrate(database_name)
120
- puts "ReleaseMigration#migrate"
121
- if File.exist?(migrations_sql)
122
- Dir.chdir(path) {
123
- puts " cd #{path}"
124
- puts " psql -d #{database_name} < #{MIGRATIONS_SQL}"
125
- Command.command "psql -d #{database_name} < #{MIGRATIONS_SQL}"
126
- }
127
- end
17
+ # Remove content of the migration/ directory
18
+ def self.clear
19
+ FileUtils.empty!(MIGRATION_DIR)
128
20
  end
129
21
 
22
+ def exist?() MigrationState.exist? end
130
23
 
131
- def self.files(path)
132
- FILES.map { |file| File.join(path, file) }
24
+ def create()
25
+ files = Share.cp "migration/*", MIGRATION_DIR
26
+ state = MigrationState.new.write(version: version, base_version: base_version)
27
+ Git.add files, state.path
28
+ self
133
29
  end
134
30
 
135
- def read_features_yml(branch = nil) YAML.load(File.read(features_yml)) || [] end
136
-
137
- def append_features_yml(paths, append: true)
138
- write_features_yml(read_features_yml.insert(append ? -1 : 0, *paths))
31
+ def update(version)
32
+ state = MigrationState.new.read
33
+ @version = state.version = version
34
+ state.write
35
+ Git.add state.path
36
+ self
139
37
  end
140
38
 
141
- def write_features_yml(paths)
142
- FileUtils.cp(template_file(features_yml), features_yml)
143
- File.open(features_yml, "a") { |f| f.write(paths.to_yaml) }
144
- FileUtils.cp(template_file(features_sql), features_sql)
145
- File.open(features_sql, "a") { |f|
146
- paths.map { |path|
147
- f.puts "\\cd #{File.basename(path)}"
148
- f.puts "\\i migrate.sql" }
149
- f.puts "\\cd .."
150
- f.puts
151
- }
152
- Git.add(features_yml)
153
- Git.add(features_sql)
39
+ def migrate(database)
40
+ base_version or raise Internal, "Can't migrate from nil to #{version}"
41
+ version or raise Internal, "Can't migrate from #{base_version} to nil"
42
+ MigrationBuilder.new(database, MIGRATION_DIR).build
154
43
  end
155
44
  end
156
45
 
157
46
  class FeatureMigration < Migration
158
- FEATURE_TMPL_DIR = "features/feature"
159
- FILES = [
160
- MIGRATE_SQL = "migrate.sql",
161
- DIFF_SQL = "diff.sql"
162
- ]
47
+ attr_reader :feature
163
48
 
164
- attr_reader :name
165
- attr_reader :version
166
- attr_reader :release_migration # The enclosing release migration
167
-
168
- attr_reader :migrate_sql
169
- attr_reader :diff_sql
170
-
171
- def features_yml() release_migration.features_yml end
172
- def features_sql() release_migration.features_sql end
173
- def migrations_sql() release_migration.migrations_sql end
174
- def release_diff_sql() release_migration.diff_sql end
175
-
176
- def files() [migrate_sql, diff_sql] end
177
- def release_files() release_migration.files end
178
-
179
- def feature_versions() release_migration.feature_versions end
180
- def feature_paths() release_migration.feature_paths end
181
-
182
- def initialize(path, name: File.basename(path))
183
- Migration.feature?(path) or raise "Expected a feature path, got #{path}"
184
- super(path, FEATURE_TMPL_DIR)
185
- @name = name
186
- @version = Migration.version(path)
187
- @release_migration = ReleaseMigration.new(File.dirname(path))
188
- @migrate_sql = File.join(path, MIGRATE_SQL)
189
- @diff_sql = File.join(path, DIFF_SQL)
49
+ def initialize(feature, base_version)
50
+ super(base_version, base_version)
51
+ @feature = feature
190
52
  end
191
53
 
192
- def exist?() release_migration.exist? && super end
193
- def create()
194
- release_migration.exist? || release_migration.create
195
- super
54
+ def self.load
55
+ migration_state = MigrationState.load
56
+ feature_state = FeatureState.load
57
+ FeatureMigration.new(feature_state.feature, migration_state.base_version)
196
58
  end
197
59
 
198
- def present?() release_migration.present? && super end
199
- def prepare()
200
- release_migration.present? || release_migration.prepare
60
+ def create()
201
61
  super
202
- release_migration.append_features_yml([path])
203
- end
204
-
205
- def include_feature(migration)
206
- release_migration.include_feature(migration, append: false)
207
- end
208
-
209
- def self.files(path)
210
- release_files + FILES.map { |file| File.join(path, file) }
211
- end
212
-
213
- def self.release_files(path)
214
- ReleaseMigration.files(path)
215
- end
216
-
217
- def dump
218
- super {
219
- puts "name: #{name}"
220
- puts "release_migration: #{path}"
221
- }
62
+ files = Share.cp "migration", File.join(MIGRATION_DIR, feature)
63
+ state = FeatureState.write(feature: feature)
64
+ Git.add files, state.path
65
+ self
222
66
  end
223
67
  end
224
68
  end
225
69
 
226
70
 
227
-
228
-
229
-
230
-