prick 0.4.0 → 0.5.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 +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
|
+
|