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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/TODO +7 -0
  4. data/exe/prick +95 -33
  5. data/lib/ext/fileutils.rb +7 -0
  6. data/lib/prick.rb +5 -3
  7. data/lib/prick/builder.rb +31 -8
  8. data/lib/prick/cache.rb +34 -0
  9. data/lib/prick/constants.rb +106 -54
  10. data/lib/prick/database.rb +26 -18
  11. data/lib/prick/diff.rb +103 -25
  12. data/lib/prick/git.rb +31 -9
  13. data/lib/prick/head.rb +183 -0
  14. data/lib/prick/migration.rb +41 -181
  15. data/lib/prick/program.rb +199 -0
  16. data/lib/prick/project.rb +277 -0
  17. data/lib/prick/rdbms.rb +2 -1
  18. data/lib/prick/schema.rb +5 -10
  19. data/lib/prick/state.rb +129 -74
  20. data/lib/prick/version.rb +41 -28
  21. data/share/diff/diff.after-tables.sql +4 -0
  22. data/share/diff/diff.before-tables.sql +4 -0
  23. data/share/diff/diff.tables.sql +8 -0
  24. data/share/migration/diff.tables.sql +8 -0
  25. data/share/{release_migration → migration}/features.yml +0 -0
  26. data/share/migration/migrate.sql +3 -0
  27. data/share/{release_migration → migration}/migrate.yml +3 -0
  28. data/share/migration/tables.sql +3 -0
  29. data/share/{schemas → schema/schema}/build.yml +0 -0
  30. data/share/{schemas → schema/schema}/prick/build.yml +0 -0
  31. data/share/schema/schema/prick/data.sql +7 -0
  32. data/share/{schemas → schema/schema}/prick/schema.sql +0 -0
  33. data/share/{schemas → schema/schema}/prick/tables.sql +2 -2
  34. data/share/{schemas → schema/schema}/public/.keep +0 -0
  35. data/share/{schemas → schema/schema}/public/build.yml +0 -0
  36. data/share/{schemas → schema/schema}/public/schema.sql +0 -0
  37. data/test_refactor +34 -0
  38. metadata +22 -20
  39. data/file +0 -0
  40. data/lib/prick/build.rb +0 -376
  41. data/lib/prick/migra.rb +0 -22
  42. data/share/feature_migration/diff.sql +0 -2
  43. data/share/feature_migration/migrate.sql +0 -2
  44. data/share/release_migration/diff.sql +0 -3
  45. data/share/release_migration/migrate.sql +0 -5
  46. data/share/schemas/prick/data.sql +0 -7
@@ -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 ||= begin
17
- version =
18
- if exist? && Rdbms.exist_table?(name, "prick", "versions")
19
- Rdbms.select(name, "select version from prick.versions")&.first&.first
20
- else
21
- nil
22
- end
23
- version && Version.new(version)
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 save(file) Rdbms.save(name, file); @version = nil end
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 to_s() name end
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
- # include Ensure
64
+ def clean() recreate end # TODO: Find solution that doesn't involve dropping the database
53
65
 
54
- private
55
- @states = {
56
- exist: [:create, :drop],
57
- loaded: [:exist, :load, :recreate]
58
- }
66
+ def to_s() name end
59
67
  end
60
68
  end
@@ -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
- @diffed = false
6
- @diff = nil
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
- # Return the diff between the two databases or nil if they're equal
20
- def read
21
- @diffed ? @diff : do_diff
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
- # Write the diff between the databases to the given file. Return true if
25
- # the databases are equal
26
- def write(file)
27
- if @diffed
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
- do_diff(file)
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
- file_arg = file ? ">#{file}" : ""
38
- command = "migra --unsafe postgres:///#{@db1} postgres:///#{@db2} #{file_arg}"
39
- stdout = Command.command command, fail: false
40
- [0,2].include? Command.status or
41
- raise Fail, "migra command failed with status #{Command.status}: #{command}"
42
- @result = Command.status == 0
43
- @diffed = !file
44
- @diff = @diffed && !@result ? stdout : nil
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
+
@@ -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
- 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
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(file)
167
- Dir.chdir(File.dirname(file)) {
168
- 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
+ }
169
188
  }
170
189
  end
171
190
 
172
- def self.rm_rf(file)
173
- Dir.chdir(File.dirname(file)) {
174
- 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
+ }
175
197
  }
176
198
  end
177
199
 
@@ -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
+