prick 0.2.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 +7 -0
- data/.gitignore +28 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/README.md +35 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/doc/create_release.txt +17 -0
- data/doc/flow.txt +98 -0
- data/doc/migra +1 -0
- data/doc/migrations.txt +172 -0
- data/doc/notes.txt +116 -0
- data/doc/sh.prick +316 -0
- data/exe/prick +467 -0
- data/file +0 -0
- data/lib/ext/algorithm.rb +14 -0
- data/lib/ext/fileutils.rb +8 -0
- data/lib/ext/pg.rb +18 -0
- data/lib/prick.rb +21 -0
- data/lib/prick/archive.rb +124 -0
- data/lib/prick/build.rb +376 -0
- data/lib/prick/command.rb +85 -0
- data/lib/prick/constants.rb +199 -0
- data/lib/prick/database.rb +58 -0
- data/lib/prick/dsort.rb +151 -0
- data/lib/prick/ensure.rb +119 -0
- data/lib/prick/exceptions.rb +13 -0
- data/lib/prick/git.rb +159 -0
- data/lib/prick/migra.rb +22 -0
- data/lib/prick/migration.rb +230 -0
- data/lib/prick/project.rb +444 -0
- data/lib/prick/rdbms.rb +147 -0
- data/lib/prick/schema.rb +100 -0
- data/lib/prick/version.rb +133 -0
- data/make_releases +369 -0
- data/prick.gemspec +46 -0
- data/share/features/diff.sql +2 -0
- data/share/features/feature/diff.sql +2 -0
- data/share/features/feature/migrate.sql +2 -0
- data/share/features/features.sql +2 -0
- data/share/features/features.yml +2 -0
- data/share/features/migrations.sql +4 -0
- data/share/gitignore +2 -0
- data/share/schemas/prick/data.sql +8 -0
- data/share/schemas/prick/schema.sql +20 -0
- metadata +188 -0
data/file
ADDED
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
module Algorithm
|
3
|
+
def follow(object, sym = nil, &block)
|
4
|
+
sym.nil? == block_given? or raise "Can't use both symbol and block"
|
5
|
+
a = []
|
6
|
+
while object
|
7
|
+
a << object
|
8
|
+
object = block_given? ? yield(object) : object.send(sym)
|
9
|
+
end
|
10
|
+
a
|
11
|
+
end
|
12
|
+
|
13
|
+
module_function :follow
|
14
|
+
end
|
data/lib/ext/pg.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'pg'
|
2
|
+
|
3
|
+
class PG::Result
|
4
|
+
def value() self.getvalue(0, 0) end
|
5
|
+
|
6
|
+
def each_value(&block)
|
7
|
+
if block_given?
|
8
|
+
self.each_row { |r| yield(r.first) }
|
9
|
+
else
|
10
|
+
self.each_row.map { |r| r.first }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?() ntuples == 0 end
|
15
|
+
|
16
|
+
def to_a() self.values end
|
17
|
+
end
|
18
|
+
|
data/lib/prick.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'semantic' # https://github.com/jlindsey/semantic
|
2
|
+
|
3
|
+
require "prick/constants.rb"
|
4
|
+
require "prick/exceptions.rb"
|
5
|
+
|
6
|
+
require "prick/archive.rb"
|
7
|
+
require "prick/build.rb"
|
8
|
+
require "prick/command.rb"
|
9
|
+
require "prick/database.rb"
|
10
|
+
require "prick/dsort.rb"
|
11
|
+
require "prick/ensure.rb"
|
12
|
+
require "prick/git.rb"
|
13
|
+
require "prick/migra.rb"
|
14
|
+
require "prick/rdbms.rb"
|
15
|
+
require "prick/schema.rb"
|
16
|
+
require "prick/version.rb"
|
17
|
+
require "prick/project.rb"
|
18
|
+
|
19
|
+
module Prick
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
|
2
|
+
module Prick
|
3
|
+
class DumpFile
|
4
|
+
attr_reader :project
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :file
|
7
|
+
attr_reader :path
|
8
|
+
|
9
|
+
def initialize(project, name)
|
10
|
+
@project = project
|
11
|
+
@name = name
|
12
|
+
@file = "#{project.name}-#{name}.#{DUMP_EXT}"
|
13
|
+
@path = File.join(CACHE_DIR, @file)
|
14
|
+
end
|
15
|
+
|
16
|
+
def exist?() File.exist?(path) end
|
17
|
+
|
18
|
+
def delete() FileUtils.rm_f(path) end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
__END__
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
class Release
|
27
|
+
attr_reader :project
|
28
|
+
attr_reader :version
|
29
|
+
attr_reader :semver
|
30
|
+
|
31
|
+
# Associated Database object
|
32
|
+
attr_reader :database
|
33
|
+
|
34
|
+
# Name of release (eg. 'project-1.2.3')
|
35
|
+
attr_reader :name
|
36
|
+
|
37
|
+
# Release file (eb. 'project-1.2.3.dump.gz')
|
38
|
+
attr_reader :file
|
39
|
+
|
40
|
+
# Path to release file
|
41
|
+
def path() File.join(RELEASE_DIR, file) end
|
42
|
+
|
43
|
+
def initialize(project, version_or_semver, database: nil)
|
44
|
+
@project = project
|
45
|
+
@version, @semver = Semver::parse(version_or_semver)
|
46
|
+
@name = "#{@project.name}-#{@semver}"
|
47
|
+
@file = @name + RELEASE_DOT_EXT
|
48
|
+
@database = database || Database.new(@name, @project.user)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.glob(project)
|
52
|
+
"#{project.name}-*.#{RELEASE_EXT}"
|
53
|
+
end
|
54
|
+
|
55
|
+
# $1 matches the version
|
56
|
+
def self.re(project)
|
57
|
+
/^#{project.name}-(#{VERSION_RE.source})$/
|
58
|
+
end
|
59
|
+
|
60
|
+
# True if the release file exists
|
61
|
+
def cached?() File.exist?(path) end
|
62
|
+
|
63
|
+
def build() project.build(version) end
|
64
|
+
|
65
|
+
def unbuild() FileUtils.rm_f(path) end
|
66
|
+
|
67
|
+
# True if the database exists and is loaded
|
68
|
+
def loaded?() database.loaded? end
|
69
|
+
|
70
|
+
# Create database and load release
|
71
|
+
def load(file = nil)
|
72
|
+
file ||= path
|
73
|
+
!loaded? or raise Error, "Release #{name} is already loaded"
|
74
|
+
database.create
|
75
|
+
database.load(file)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Reload release
|
79
|
+
def reload(file = path) unload; load(file) end
|
80
|
+
|
81
|
+
# Delete a database. It is not an error if the database doesn't exists
|
82
|
+
def unload() database.drop if loaded? end
|
83
|
+
|
84
|
+
# Remove the release file
|
85
|
+
def remove() FileUtils::rm_f(path) end
|
86
|
+
|
87
|
+
# Compare two release by semantic version
|
88
|
+
def <=>(other) self.semver <=> other.semver end
|
89
|
+
|
90
|
+
# Render self a the name of the release (eg. 'project-1.2.3')
|
91
|
+
def to_s() name end
|
92
|
+
|
93
|
+
# Returns self so that you can do 'release = lookup(version).ensure(:loaded)'
|
94
|
+
def ensure(state, expect: true)
|
95
|
+
value = self.send(:"#{state}?")
|
96
|
+
if value != expect
|
97
|
+
state_methods = STATES[state] or raise Error, "Can't change state to #{state.inspect}"
|
98
|
+
index = expect ? 0 : 1
|
99
|
+
self.send(state_method[index])
|
100
|
+
end
|
101
|
+
self
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
STATES = {
|
106
|
+
cached: [:build, :unbuild],
|
107
|
+
loaded: [:load, :unload]
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
|
data/lib/prick/build.rb
ADDED
@@ -0,0 +1,376 @@
|
|
1
|
+
|
2
|
+
require "prick/ensure.rb"
|
3
|
+
require "prick/dsort.rb"
|
4
|
+
require "ext/fileutils.rb" # for ::touch_p
|
5
|
+
|
6
|
+
module Prick
|
7
|
+
class Build
|
8
|
+
include Ensure
|
9
|
+
|
10
|
+
# The associated project object
|
11
|
+
attr_reader :project
|
12
|
+
|
13
|
+
# Version
|
14
|
+
attr_reader :version
|
15
|
+
|
16
|
+
# Build name. Same as `version.to_s`
|
17
|
+
def name() version.to_s end
|
18
|
+
|
19
|
+
# Associated database object
|
20
|
+
attr_reader :database
|
21
|
+
|
22
|
+
# Schema object. Only defined when the build has been checked out
|
23
|
+
attr_reader :schema
|
24
|
+
|
25
|
+
# Migration object. Running the migration on a base release database will
|
26
|
+
# mutate it into the current release
|
27
|
+
attr_reader :migration
|
28
|
+
|
29
|
+
# Path to a filesystem node that represents the build on disk. Used to
|
30
|
+
# detect if a build is present in the current release's tree. It is
|
31
|
+
# possible to infer the base release from the node - either by a naming
|
32
|
+
# convention or by reading the file. Build::deref_node_file does that
|
33
|
+
def node() raise AbstractMethod end
|
34
|
+
|
35
|
+
# Base release. Returns nil if version is 0.0.0. Raises an exception if
|
36
|
+
# branch is not represented on disk and it can't be inferred from the
|
37
|
+
# version (only features)
|
38
|
+
def base_release()
|
39
|
+
return nil if version.zero?
|
40
|
+
@base_release.present? or raise Internal, "Release #{name} is not present"
|
41
|
+
@base_release
|
42
|
+
end
|
43
|
+
|
44
|
+
# List of features in this build. This requires the build to be present on disk
|
45
|
+
def features()
|
46
|
+
present? or raise "Build #{name} is not present"
|
47
|
+
version.zero? ? [] : migration.feature_versions.map { |version| project[version] }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return the build's history as a hash from release to list of features
|
51
|
+
def history()
|
52
|
+
h = {}
|
53
|
+
Algorithm.follow(self, :base_release).each { |release|
|
54
|
+
h[release] = []
|
55
|
+
indent {
|
56
|
+
release.features.each { |feature| h[release] << feature }
|
57
|
+
}
|
58
|
+
}
|
59
|
+
h
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(project, base_release, version, migration, database: nil, schema: nil)
|
63
|
+
base_release.nil? || base_release.is_a?(Release) or
|
64
|
+
raise Internal, "Expected a Release object, got #{base_release.class}"
|
65
|
+
version.is_a?(Version) or raise Internal, "Expected a Version object, got #{version.class}"
|
66
|
+
@project = project
|
67
|
+
@base_release = base_release
|
68
|
+
@version = version
|
69
|
+
@migration = migration
|
70
|
+
@schema = Schema.new(project)
|
71
|
+
@database = database || Database.new("#{project.name}-#{name}", project.user)
|
72
|
+
project[name] = self
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return true if the build exists as a branch in git
|
76
|
+
def exist?() Git.branch?(name) end
|
77
|
+
|
78
|
+
# Create and checkout the branch
|
79
|
+
def create()
|
80
|
+
!present? or raise Fail, "Build #{name} is already present on disk"
|
81
|
+
!exist? or raise Fail, "Build #{name} is already present in git"
|
82
|
+
Git.create_branch(name)
|
83
|
+
Git.checkout_branch(name)
|
84
|
+
end
|
85
|
+
|
86
|
+
# FIXME: Kills the current branch under the feets of the application. Also doesn't update
|
87
|
+
# internal structures in Project
|
88
|
+
def destroy()
|
89
|
+
project.release != self or raise Error, "Can't destroy current branch - #{self.version}"
|
90
|
+
Git.delete_branch(name)
|
91
|
+
end
|
92
|
+
|
93
|
+
# True if the release is present in this git branch
|
94
|
+
def present?() File.exist?(node) end
|
95
|
+
|
96
|
+
# True if the release is the active branch
|
97
|
+
def active?() Git.current_branch == name end
|
98
|
+
|
99
|
+
def checkout()
|
100
|
+
Git.checkout(name)
|
101
|
+
end
|
102
|
+
|
103
|
+
def checkback() # Doubtfull - creates strange results on Release and Prerelease branches
|
104
|
+
base_release.checkout
|
105
|
+
end
|
106
|
+
|
107
|
+
def built?()
|
108
|
+
active? && @schema.built?(@database)
|
109
|
+
end
|
110
|
+
|
111
|
+
def build()
|
112
|
+
active? or raise Error, "Can't build: Not active"
|
113
|
+
@schema.build(@database)
|
114
|
+
end
|
115
|
+
|
116
|
+
def rebuild()
|
117
|
+
active? or raise Error, "Can't rebuild: Not active"
|
118
|
+
@database.recreate
|
119
|
+
build
|
120
|
+
end
|
121
|
+
|
122
|
+
def include_feature(feature)
|
123
|
+
migration.include_feature(feature.migration)
|
124
|
+
end
|
125
|
+
|
126
|
+
def remove_feature(feature)
|
127
|
+
raise NotYet
|
128
|
+
end
|
129
|
+
|
130
|
+
# Create a copy of the project in tmp/ and checkout the branch. Used to build
|
131
|
+
# releases. Returns the path to the copy
|
132
|
+
def snapshot() end
|
133
|
+
|
134
|
+
# Sorting
|
135
|
+
def <=>(other) version <=> other.version end
|
136
|
+
|
137
|
+
# Use #name for String conversion
|
138
|
+
def to_s() name end
|
139
|
+
|
140
|
+
# Reads the name of the base release from a node (see Build#node)
|
141
|
+
def self.deref_node_file(node)
|
142
|
+
if File.basename(node) == "0.0.0"
|
143
|
+
nil
|
144
|
+
elsif File.symlink?(node) # Releases and prereleases
|
145
|
+
symlink = Command.command("readlink -v #{node}").first
|
146
|
+
value = File.basename(symlink.sub(/\/$/, ""))
|
147
|
+
value == "/dev/null" ? nil : value
|
148
|
+
elsif File.directory?(node) # Migrations
|
149
|
+
name = File.basename(node)
|
150
|
+
name =~ MIGRATION_RE or raise "Illegal migration name: #{name}"
|
151
|
+
$1
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
@states = {
|
157
|
+
exist: [:create, :destroy],
|
158
|
+
initialized: [:exist, :initialize, false], # ???
|
159
|
+
active: [:exist, :checkout, :checkback],
|
160
|
+
# built: [:active, :build, lambda { |this| this.database.recreate } ],
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
class AbstractRelease < Build
|
165
|
+
# Tag
|
166
|
+
def tag() [version.custom, "v#{version.semver}"].compact.join("-") end
|
167
|
+
|
168
|
+
# Cache object
|
169
|
+
attr_reader :archive
|
170
|
+
|
171
|
+
# Redefine Build#node
|
172
|
+
attr_reader :node
|
173
|
+
|
174
|
+
# The directory representing this release. It is initially empty. It is the
|
175
|
+
# same as the next release's migration directory and contains features that
|
176
|
+
# require this release
|
177
|
+
def release_dir() raise AbstractMethod end
|
178
|
+
|
179
|
+
def initialize(project, base_release, version, migration, **opts)
|
180
|
+
super
|
181
|
+
@node = File.join(RELEASE_DIR, name)
|
182
|
+
@archive = DumpFile.new(project, version.to_s)
|
183
|
+
end
|
184
|
+
|
185
|
+
def cached?() archive.exist? end
|
186
|
+
def cache() database.save(archive.path) end
|
187
|
+
def uncache() FileUtils.rm_f(archive.path) end
|
188
|
+
|
189
|
+
def loaded?() schema.loaded?(database) end
|
190
|
+
def load() database.ensure(:loaded, archive.file) end
|
191
|
+
def unload() database.ensure(:loaded, expect: false) end
|
192
|
+
|
193
|
+
def prepare(commit: true)
|
194
|
+
migration.prepare
|
195
|
+
Git.commit("Prepared next release") if commit
|
196
|
+
end
|
197
|
+
|
198
|
+
def <=>(other) version <=> other.version end
|
199
|
+
|
200
|
+
# Create the release in Git and on the disk
|
201
|
+
def create(create_release_link_file: true)
|
202
|
+
super()
|
203
|
+
|
204
|
+
# Create release link file (eg. releases/0.1.0)
|
205
|
+
if create_release_link_file
|
206
|
+
base_release_dir = (version.zero? ? "/dev/null" : "../#{migration.path}")
|
207
|
+
Dir.chdir(RELEASE_DIR) {
|
208
|
+
FileUtils.ln_s(base_release_dir, File.basename(node))
|
209
|
+
}
|
210
|
+
Git.add(node)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Set schema version
|
214
|
+
project.schema.version = version
|
215
|
+
Git.add(project.schema.path)
|
216
|
+
end
|
217
|
+
|
218
|
+
def dump
|
219
|
+
return self
|
220
|
+
$stderr.puts "#{self.class} #{version}"
|
221
|
+
$stderr.indent { |f|
|
222
|
+
f.puts "node : #{node.inspect}"
|
223
|
+
f.puts "base_release: #{base_release&.version.inspect}"
|
224
|
+
f.puts "migration : #{migration&.path.inspect}"
|
225
|
+
f.puts "release_dir : #{release_dir.inspect}"
|
226
|
+
}
|
227
|
+
self
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
@ensure_states = {
|
232
|
+
# cached: [:built, :cache, :uncache],
|
233
|
+
loaded: [:cached, :load, :unload]
|
234
|
+
}
|
235
|
+
end
|
236
|
+
|
237
|
+
class Release < AbstractRelease
|
238
|
+
def release_dir() File.join(FEATURE_DIR, name) end
|
239
|
+
|
240
|
+
def initialize(project, base_release, version)
|
241
|
+
migration = base_release && ReleaseMigration.new(base_release.release_dir)
|
242
|
+
super(project, base_release, version, migration)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Create the release in Git and on the disk. We assume that the migration exists
|
246
|
+
def create
|
247
|
+
super
|
248
|
+
|
249
|
+
# Create migration link file
|
250
|
+
if !version.zero?
|
251
|
+
migration_version = "#{base_release.version}_#{version}"
|
252
|
+
features = "../#{migration.path}"
|
253
|
+
Dir.chdir(MIGRATION_DIR) {
|
254
|
+
FileUtils.ln_s(features, migration_version)
|
255
|
+
Git.add(migration_version)
|
256
|
+
}
|
257
|
+
end
|
258
|
+
|
259
|
+
# Create new empty feature directory
|
260
|
+
ReleaseMigration.new(release_dir).create
|
261
|
+
|
262
|
+
Git.commit("Release #{version}")
|
263
|
+
Git.create_tag(version)
|
264
|
+
dump
|
265
|
+
migration.dump if migration
|
266
|
+
self
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# TODO: Rename to PreRelease
|
271
|
+
class Prerelease < AbstractRelease
|
272
|
+
attr_reader :target_release
|
273
|
+
|
274
|
+
def release_dir() base_release.release_dir end
|
275
|
+
|
276
|
+
def initialize(project, base_release, version, target_version = version.truncate(:pre))
|
277
|
+
@target_release = Release.new(project, base_release, target_version)
|
278
|
+
migration = ReleaseMigration.new(target_release.migration.path)
|
279
|
+
super(project, base_release, version, migration)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Create the pre-release in Git and on disk
|
283
|
+
def create
|
284
|
+
super
|
285
|
+
migration.prepare
|
286
|
+
Git.commit("Pre-release #{version}")
|
287
|
+
dump
|
288
|
+
self
|
289
|
+
end
|
290
|
+
|
291
|
+
# Create a migration for this release
|
292
|
+
def prepare_migration
|
293
|
+
base_release.built? or raise "Base release #{base_release} is not built"
|
294
|
+
puts "Prerelease#generate_migration"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
class MigrationPrerelease < Prerelease
|
299
|
+
def initialize(project, base_release, version, target_version = version.truncate(:pre))
|
300
|
+
release_dir = "/migrations/..."
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
class MigrationRelease < AbstractRelease
|
305
|
+
def create
|
306
|
+
# does not call super because migrations belong to the release they migrate to
|
307
|
+
#
|
308
|
+
# code, code, code
|
309
|
+
end
|
310
|
+
|
311
|
+
def include_feature
|
312
|
+
raise NotYet
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
class Feature < Build
|
317
|
+
# Name of feature
|
318
|
+
def feature() version.feature end
|
319
|
+
|
320
|
+
# A feature's node is the feature directory
|
321
|
+
def node() release_dir end
|
322
|
+
|
323
|
+
def release_dir() migration.path end
|
324
|
+
|
325
|
+
def initialize(project, base_release, name)
|
326
|
+
base_release.is_a?(Release) || base_release.is_a?(Prerelease) or
|
327
|
+
raise Internal, "Expected a Release object, got #{base_release.class}"
|
328
|
+
version = Version.new(base_release.version, feature: name)
|
329
|
+
migration = FeatureMigration.new(Migration.path(version))
|
330
|
+
super(project, base_release, version, migration)
|
331
|
+
end
|
332
|
+
|
333
|
+
def checkout()
|
334
|
+
super
|
335
|
+
# FileUtils.ln_sf(feature, "feature")
|
336
|
+
end
|
337
|
+
|
338
|
+
def create
|
339
|
+
super
|
340
|
+
migration.create
|
341
|
+
migration.prepare
|
342
|
+
Git.commit("Created feature #{feature}")
|
343
|
+
migration.dump
|
344
|
+
self
|
345
|
+
end
|
346
|
+
|
347
|
+
# features/
|
348
|
+
# 0.0.0/
|
349
|
+
# feature_a
|
350
|
+
# feature_b/
|
351
|
+
# base_release.prick
|
352
|
+
# name.yml <- 0.2.0/feature_b
|
353
|
+
# rebased.yml <- reads base_release here
|
354
|
+
#
|
355
|
+
# 0.2.0/
|
356
|
+
# feature_a -> 0.0.0/feature_a <- not rebased
|
357
|
+
# feature_b -> 0.0.0/feature_b <- rebased
|
358
|
+
|
359
|
+
|
360
|
+
def rebase(new_base_release)
|
361
|
+
# Checkout new_base_release
|
362
|
+
# Merge feature
|
363
|
+
# Establish symlinks
|
364
|
+
# Create as branch
|
365
|
+
|
366
|
+
# new_base > base_release or
|
367
|
+
# raise Error, "Can't rebase from #{base_release.version} to #{new_base.version}"
|
368
|
+
# new_feature = Feature.new(project, base_release, base.version, base: base)
|
369
|
+
# new_feature.ensure(:active)
|
370
|
+
# schema.version = version
|
371
|
+
# FileUtils.ln_sf("../#{feature.release_dir}", new_feature.release_dir)
|
372
|
+
# Git.add(new_feature.release_dir)
|
373
|
+
# new_feature
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|