prick 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/TODO +7 -0
- data/exe/prick +95 -33
- data/lib/ext/fileutils.rb +7 -0
- data/lib/prick.rb +5 -3
- data/lib/prick/builder.rb +31 -8
- data/lib/prick/cache.rb +34 -0
- data/lib/prick/constants.rb +106 -54
- data/lib/prick/database.rb +26 -18
- data/lib/prick/diff.rb +103 -25
- data/lib/prick/git.rb +31 -9
- data/lib/prick/head.rb +183 -0
- data/lib/prick/migration.rb +41 -181
- data/lib/prick/program.rb +199 -0
- data/lib/prick/project.rb +277 -0
- data/lib/prick/rdbms.rb +2 -1
- data/lib/prick/schema.rb +5 -10
- data/lib/prick/state.rb +129 -74
- data/lib/prick/version.rb +41 -28
- data/share/diff/diff.after-tables.sql +4 -0
- data/share/diff/diff.before-tables.sql +4 -0
- data/share/diff/diff.tables.sql +8 -0
- data/share/migration/diff.tables.sql +8 -0
- data/share/{release_migration → migration}/features.yml +0 -0
- data/share/migration/migrate.sql +3 -0
- data/share/{release_migration → migration}/migrate.yml +3 -0
- data/share/migration/tables.sql +3 -0
- data/share/{schemas → schema/schema}/build.yml +0 -0
- data/share/{schemas → schema/schema}/prick/build.yml +0 -0
- data/share/schema/schema/prick/data.sql +7 -0
- data/share/{schemas → schema/schema}/prick/schema.sql +0 -0
- data/share/{schemas → schema/schema}/prick/tables.sql +2 -2
- data/share/{schemas → schema/schema}/public/.keep +0 -0
- data/share/{schemas → schema/schema}/public/build.yml +0 -0
- data/share/{schemas → schema/schema}/public/schema.sql +0 -0
- data/test_refactor +34 -0
- metadata +22 -20
- data/file +0 -0
- data/lib/prick/build.rb +0 -376
- data/lib/prick/migra.rb +0 -22
- data/share/feature_migration/diff.sql +0 -2
- data/share/feature_migration/migrate.sql +0 -2
- data/share/release_migration/diff.sql +0 -3
- data/share/release_migration/migrate.sql +0 -5
- data/share/schemas/prick/data.sql +0 -7
data/lib/prick/database.rb
CHANGED
@@ -7,20 +7,25 @@ module Prick
|
|
7
7
|
attr_reader :user
|
8
8
|
|
9
9
|
def initialize(name, user)
|
10
|
+
name != "" or raise "Illegal database name"
|
10
11
|
@name = name
|
11
12
|
@user = user
|
12
13
|
@version = nil
|
13
14
|
end
|
14
15
|
|
15
|
-
def version
|
16
|
-
@version
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
def version(cache: true)
|
17
|
+
if cache && @version
|
18
|
+
@version
|
19
|
+
else
|
20
|
+
@version = begin
|
21
|
+
version =
|
22
|
+
if exist? && Rdbms.exist_table?(name, "prick", "versions")
|
23
|
+
Rdbms.select(name, "select version from prick.versions")&.first&.first
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
version && Version.new(version)
|
28
|
+
end
|
24
29
|
end
|
25
30
|
end
|
26
31
|
|
@@ -43,18 +48,21 @@ module Prick
|
|
43
48
|
def drop() Rdbms.drop_database(name, fail: false); @version = nil end
|
44
49
|
|
45
50
|
def loaded?() !version.nil? end
|
46
|
-
def load(file) Rdbms.load(name, file, user: user); @version = nil end
|
47
51
|
|
48
|
-
def
|
52
|
+
def load(file)
|
53
|
+
!loaded? or raise Internal, "Database #{name} is already loaded"
|
54
|
+
Rdbms.load(name, file, user: user)
|
55
|
+
version # Provoke load of version
|
56
|
+
end
|
49
57
|
|
50
|
-
def
|
58
|
+
def save(file)
|
59
|
+
loaded? or raise Internal, "Database #{name} is not loaded"
|
60
|
+
Rdbms.save(name, file)
|
61
|
+
@version = nil
|
62
|
+
end
|
51
63
|
|
52
|
-
#
|
64
|
+
def clean() recreate end # TODO: Find solution that doesn't involve dropping the database
|
53
65
|
|
54
|
-
|
55
|
-
@states = {
|
56
|
-
exist: [:create, :drop],
|
57
|
-
loaded: [:exist, :load, :recreate]
|
58
|
-
}
|
66
|
+
def to_s() name end
|
59
67
|
end
|
60
68
|
end
|
data/lib/prick/diff.rb
CHANGED
@@ -1,47 +1,125 @@
|
|
1
1
|
module Prick
|
2
2
|
class Diff
|
3
|
+
# Diff as a list of lines
|
4
|
+
attr_reader :diff
|
5
|
+
|
6
|
+
# Subject of the diff
|
7
|
+
attr_reader :before_table_changes
|
8
|
+
attr_reader :table_changes
|
9
|
+
attr_reader :after_table_changes
|
10
|
+
|
3
11
|
def initialize(db1, db2)
|
4
12
|
@db1, @db2 = db1, db2
|
5
|
-
|
6
|
-
|
7
|
-
@result = nil
|
13
|
+
do_diff
|
14
|
+
split_on_table_changes
|
8
15
|
end
|
9
16
|
|
10
17
|
def self.same?(db1, db2) Diff.new(db1, db2).same? end
|
11
18
|
|
12
19
|
# Return true if the two databases are equal. Named #same? to avoid name
|
13
20
|
# collision with the built in #equal?
|
14
|
-
def same?
|
15
|
-
do_diff if @result.nil?
|
16
|
-
@result
|
17
|
-
end
|
21
|
+
def same?() diff.empty? end
|
18
22
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
23
|
+
# Write the diff between the databases to the given file(s). Return true if
|
24
|
+
# the databases are equal
|
25
|
+
def write_segments(file1, file2 = nil, file3 = nil, mark: true)
|
26
|
+
if file2.nil? && file3.nil?
|
27
|
+
file2 = file3 = file1
|
28
|
+
elsif file2.nil? ^ !file3.nil?
|
29
|
+
raise Internal, "Either none or both of `file2` and `file3` should be nil"
|
30
|
+
end
|
31
|
+
write_segment(file1, :before_table_changes, mark: mark)
|
32
|
+
write_segment(file2, :table_changes, mark: mark)
|
33
|
+
write_segment(file3, :after_table_changes, mark: mark)
|
22
34
|
end
|
23
35
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
IO.write(file, @diff)
|
36
|
+
def write_segment(file, segment, mark: true)
|
37
|
+
lines = self.send(segment)
|
38
|
+
if lines.empty?
|
39
|
+
FileUtils.rm_f(file) if file != "/dev/stdout"
|
29
40
|
else
|
30
|
-
|
41
|
+
File.open(file, "w") { |f|
|
42
|
+
f.puts "--" + segment.to_s.gsub("_", " ").upcase if mark
|
43
|
+
f.puts lines
|
44
|
+
}
|
31
45
|
end
|
32
|
-
@result
|
33
46
|
end
|
34
47
|
|
35
48
|
private
|
36
49
|
def do_diff(file = nil)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
50
|
+
command = "migra --unsafe --with-privileges postgres:///#{@db1} postgres:///#{@db2}"
|
51
|
+
@diff = Command.command(command, fail: false)
|
52
|
+
[0,2].include?(Command.status) or
|
53
|
+
raise Internal, "migrate command failed with status #{Command.status}: #{command}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Initialize table change variables
|
57
|
+
def split_on_table_changes
|
58
|
+
@before_table_changes = []
|
59
|
+
@table_changes = []
|
60
|
+
@after_table_changes = []
|
61
|
+
|
62
|
+
before = true
|
63
|
+
tables = false
|
64
|
+
after = false
|
65
|
+
|
66
|
+
in_table = false
|
67
|
+
|
68
|
+
diff.each { |l|
|
69
|
+
if before
|
70
|
+
if l =~ /^alter table/
|
71
|
+
@table_changes << l
|
72
|
+
before = false
|
73
|
+
tables = true
|
74
|
+
elsif l =~ /^create table/
|
75
|
+
@table_changes << l
|
76
|
+
before = false
|
77
|
+
tables = true
|
78
|
+
in_table = true
|
79
|
+
elsif l =~ /^drop table/
|
80
|
+
@table_changes << l
|
81
|
+
before = false
|
82
|
+
tables = true
|
83
|
+
else
|
84
|
+
@before_table_changes << l
|
85
|
+
end
|
86
|
+
elsif tables
|
87
|
+
if in_table
|
88
|
+
if l =~ /^\)/
|
89
|
+
@table_changes << l
|
90
|
+
in_table = false
|
91
|
+
else
|
92
|
+
@table_changes << l
|
93
|
+
end
|
94
|
+
elsif l =~ /^alter table/
|
95
|
+
@table_changes << l
|
96
|
+
elsif l =~ /^create table/
|
97
|
+
@table_changes << l
|
98
|
+
in_table = true
|
99
|
+
elsif l =~ /^drop table/
|
100
|
+
@table_changes << l
|
101
|
+
elsif l =~ /^\s*$/
|
102
|
+
@table_changes << l
|
103
|
+
else
|
104
|
+
@after_table_changes << l
|
105
|
+
tables = false
|
106
|
+
after = true
|
107
|
+
end
|
108
|
+
else
|
109
|
+
@after_table_changes << l
|
110
|
+
end
|
111
|
+
}
|
45
112
|
end
|
46
113
|
end
|
47
114
|
end
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
|
data/lib/prick/git.rb
CHANGED
@@ -88,6 +88,11 @@ module Prick
|
|
88
88
|
Command.command "git branch #{name}"
|
89
89
|
end
|
90
90
|
|
91
|
+
# Rename a branch
|
92
|
+
def self.rename_branch(from, to)
|
93
|
+
Command.command "git branch -m #{from} #{to}"
|
94
|
+
end
|
95
|
+
|
91
96
|
# Destroy branch
|
92
97
|
def self.delete_branch(name)
|
93
98
|
Command.command "git branch -D #{name}", fail: false
|
@@ -127,9 +132,13 @@ module Prick
|
|
127
132
|
merge_branch(name, exclude_files: exclude_files, fail: fail)
|
128
133
|
end
|
129
134
|
|
130
|
-
# List branches
|
131
|
-
def self.list_branches
|
132
|
-
|
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
|
133
142
|
end
|
134
143
|
|
135
144
|
# Add a file to the index of the current branch
|
@@ -141,6 +150,14 @@ module Prick
|
|
141
150
|
}
|
142
151
|
end
|
143
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
|
+
|
144
161
|
# Return content of file in the given tag or branch. Defaults to HEAD
|
145
162
|
def self.readlines(file, tag: nil, branch: nil)
|
146
163
|
!(tag && branch) or raise Internal, "Can't use both tag: and branch: options"
|
@@ -163,15 +180,20 @@ module Prick
|
|
163
180
|
end.join("\n") + "\n"
|
164
181
|
end
|
165
182
|
|
166
|
-
def self.rm(
|
167
|
-
|
168
|
-
|
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
|
+
}
|
169
188
|
}
|
170
189
|
end
|
171
190
|
|
172
|
-
def self.rm_rf(
|
173
|
-
|
174
|
-
|
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
|
+
}
|
175
197
|
}
|
176
198
|
end
|
177
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
|
+
|