Class: Prick::Project

Inherits:
Object
  • Object
show all
Defined in:
lib/prick/project.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, user, branch) ⇒ Project

Returns a new instance of Project.



35
36
37
38
39
40
# File 'lib/prick/project.rb', line 35

def initialize(name, user, branch)
  @name = name
  @user = user
  @branch = branch
  @database = Database.new(name, user)
end

Instance Attribute Details

#branchObject (readonly)

Current branch



15
16
17
# File 'lib/prick/project.rb', line 15

def branch
  @branch
end

#messageObject (readonly)

Last commit message. TODO: Move to git.rb



26
27
28
# File 'lib/prick/project.rb', line 26

def message
  @message
end

#nameObject (readonly)

Name of project. Persisted in the project state file



8
9
10
# File 'lib/prick/project.rb', line 8

def name
  @name
end

#userObject (readonly)

Name of Postgresql user that owns the databases. Defaults to #name. Persisted in the project state file. TODO: Make writable



12
13
14
# File 'lib/prick/project.rb', line 12

def user
  @user
end

Class Method Details

.initialize_directory(name, user, directory) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/prick/project.rb', line 70

def self.initialize_directory(name, user, directory)
  FileUtils.mkdir_p(directory)
  Dir.chdir(directory) {
    # Initialize git instance
    Git.init

    # Create prick version file
    PrickVersion.new.write(VERSION)

    # Create project state file
    ProjectState.new(name: name, user: user).write

    # Directories
    DIRS.each { |dir|
      !File.exist?(dir) or raise Fail, "Already initialized: Directory '#{dir}' exists"
    }
    FileUtils.mkdir_p(DIRS)
    DIRS.each { |dir| FileUtils.touch("#{dir}/.keep") }

    # Copy default gitignore and schema files
    Share.cp("gitignore", ".gitignore")
    Share.cp("schemas", ".")

    # Add everything so far
    Git.add(".")
    Git.commit("Initial import")

    # Create initial release
    release = Release.new(Version.zero, nil)
    release.create

    schema = Schema.new(SCHEMAS_DIR)
    schema.version = release.version
    Git.add schema.version_file

    Git.commit "Release 0.0.0"
    release.tag!

    # Kill master branch
    Git.delete_branch("master")
  }
end

.initialized?(directory) ⇒ Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/prick/project.rb', line 66

def self.initialized?(directory) 
  File.directory?(directory) && Dir.chdir(directory) { ProjectState.new.exist? } 
end

.loadObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/prick/project.rb', line 42

def self.load
  name = Git.current_branch || Git.current_tag
  if name =~ MIGRATION_RE
    branch = MigrationRelease.load(name)
  else
    begin
      version = Version.new(name)
    rescue Version::FormatError
      raise Fail, "Illegal branch name: #{name}"
    end
    if version.release?
      branch = Release.load(name)
    elsif version.pre?
      branch = PreRelease.load(name, version)
    elsif version.feature?
      branch = Feature.load(name)
    else
      raise Oops
    end
  end
  state = ProjectState.new.read
  Project.new(state.name, state.user, branch)
end

.tag?Boolean

Returns:

  • (Boolean)


30
# File 'lib/prick/project.rb', line 30

def self.tag?() Git.detached? end

Instance Method Details

#backup(path = nil) ⇒ Object



149
150
151
# File 'lib/prick/project.rb', line 149

def backup(path = nil)
  save path || File.join(SPOOL_DIR, "#{name}-#{Time.now.utc.strftime("%Y%m%d-%H%M%S")}.sql.gz")
end

#build(database: self.database, version: nil) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/prick/project.rb', line 113

def build(database: self.database, version: nil)
  clean(database)
  if version
    FileUtils.mkdir_p(TMP_DIR)
    Dir.mktmpdir("clone-", TMP_DIR) { |dir|
      Command.command "git clone . #{dir}"
      Dir.chdir(dir) {
        Git.checkout_tag(version)
        project = Project.load
        project.branch.build(database)
      }
    }
  else
    branch.build(database)
  end
  self
end

#cache_file(version) ⇒ Object



142
# File 'lib/prick/project.rb', line 142

def cache_file(version) File.join(CACHE_DIR, "#{database(version)}.sql.gz") end

#cancel_release(version, commit: true) ⇒ Object



225
226
227
# File 'lib/prick/project.rb', line 225

def cancel_release(version, commit: true)
  Git.cancel_tag(version)
end

#check_migration(from_version) ⇒ Object

Checks that the migration of the current branch takes the database from the base version to the content of the schema



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/prick/project.rb', line 275

def check_migration(from_version)
  begin
    from_db = Database.new("#{name}-base", user)
    to_db = Database.new("#{name}-next", user)
    build(database: from_db, version: from_version)
    build(database: to_db)
    migration = # FIXME: This may be a repeated pattern
        if migration?
          branch.migration
        elsif prerelease?
          branch.migration
        else
          Migration.new(nil, branch.directory)
        end
    migration.migrate(from_db)
    Diff.new(from_db, to_db).same?
  ensure
    from_db&.drop
    to_db&.drop
  end
end

#create_feature(name, commit: true) ⇒ Object

# Turned out to be a bad idea

def retarget_migration(version, commit: true)
  branch.retarget(version)
  commit "Retargeted to #{version}" if commit
  branch
end


262
263
264
265
266
# File 'lib/prick/project.rb', line 262

def create_feature(name, commit: true)
  feature = Feature.new(name, version)
  feature.create
  submit "Created feature #{name}", commit
end

#create_prerelease(version, commit: true) ⇒ Object



239
240
241
242
243
244
245
# File 'lib/prick/project.rb', line 239

def create_prerelease(version, commit: true)
  prerelease = PreRelease.new(version.increment(:pre), branch.version)
  prerelease.create
  submit "Created pre-release #{prerelease.version}", commit
  build
  prerelease
end

#create_release(version, commit: true) ⇒ Object



215
216
217
218
219
220
221
222
223
# File 'lib/prick/project.rb', line 215

def create_release(version, commit: true)
  check_migration(branch.version) or raise Error, "Schema/migration mismatch"
  release = Release.new(version, branch.version)
  release.create
  submit "Created release #{version}", commit
  release.tag!
  build
  release
end

#create_release_from_prerelease(commit: true) ⇒ Object



229
230
231
232
233
234
235
236
237
# File 'lib/prick/project.rb', line 229

def create_release_from_prerelease(commit: true)
  check_migration(branch.base_version) or raise Error, "Schema/migration mismatch"
  release = Release.new(branch.version.truncate(:pre), branch.base_version)
  release.create
  submit "Released #{release.version}", commit
  release.tag!
  build
  release
end

#database(version = nil) ⇒ Object

Project (default) database



21
22
23
# File 'lib/prick/project.rb', line 21

def database(version = nil)
  version ? Database.new("#{name}-#{version.truncate(:pre)}", user) : @database
end

#include_feature(feature_version, commit: false) ⇒ Object



268
269
270
271
# File 'lib/prick/project.rb', line 268

def include_feature(feature_version, commit: false)
  Git.merge_branch(feature_version, exclude_files: [branch.schema.version_file], fail: true)
  branch.include(feature_version)
end

#increment_prerelease(commit: true) ⇒ Object



247
248
249
250
251
252
253
# File 'lib/prick/project.rb', line 247

def increment_prerelease(commit: true)
  prerelease = branch.increment
  prerelease.create
  submit "Created pre-release #{prerelease.version}", commit
  build
  prerelease
end

#list_migrationsObject

TODO list_migrations(all: false)



324
325
326
327
328
329
330
# File 'lib/prick/project.rb', line 324

def list_migrations
  Git.list_branches.grep(MIGRATION_RE) { |branch|
    from_version = Version.new($1)
    to_version = Version.new($4)
    MigrationRelease.new(to_version, from_version)
  }
end

#list_releases(all: false) ⇒ Object



313
314
315
316
317
318
319
320
# File 'lib/prick/project.rb', line 313

def list_releases(all: false)
  Git.list_tags(include_cancelled: all).grep(RELEASE_RE) { |tag|
    version = $2
    dir = File.join(RELEASES_DIR, version)
    migration_state = MigrationState.new(dir).read(tag: tag)
    Release.new(migration_state.version, migration_state.base_version)
  }
end

#list_upgrades(from, to) ⇒ Object



332
333
334
335
# File 'lib/prick/project.rb', line 332

def list_upgrades(from, to)
  edges = (list_releases + list_migrations).map { |branch| [branch.base_version, branch.version, branch] }
  (Algorithm.shortest_path(edges, from, to) || []).map(&:last)
end

#load(file, database: self.database) ⇒ Object



131
132
133
134
135
# File 'lib/prick/project.rb', line 131

def load(file, database: self.database)
  clean(database)
  database.load(file)
  self
end

#make(database, subject) ⇒ Object



144
145
146
147
# File 'lib/prick/project.rb', line 144

def make(database, subject)
  Schema.built? or raise Error, "Schema is not built into project database"
  Schema.make(database, subject)
end

#prepare_diff(from_version) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/prick/project.rb', line 172

def prepare_diff(from_version)
  to_database = nil
  begin
    from_name = "#{name}-base"
    from_database = Database.new(from_name, user)
    build(database: from_database, version: from_version)

    to_name = "#{name}-next"
    to_database = Database.new(to_name, user)
    build(database: to_database)

    # Helpful double-check
    Diff.same?(to_database.name, database.name) or 
        raise Error, "Schema and project database are not synchronized"

    if release?
      dir = branch.migration.migration_dir
    else
      dir = branch.migration.features_dir
    end

#       dir = branch.migration.features_dir || branch.migration.migration_dir
    to_file = File.join(dir, DIFF_FILE)

    if prerelease?
      branch.migrate_features(from_database)
    end

    Diff.new(from_name, to_name).write(to_file)
  ensure
    from_database&.drop
    to_database&.drop
  end
end

#prepare_migration(from, commit: true) ⇒ Object



207
208
209
210
211
212
213
# File 'lib/prick/project.rb', line 207

def prepare_migration(from, commit: true)
  to = branch.version
  migration_release = MigrationRelease.new(to, from)
  migration_release.create
  submit "Prepared migration from #{from} to #{to}", commit
  migration_release
end

#prepare_release(commit: true) ⇒ Object



157
158
159
160
161
# File 'lib/prick/project.rb', line 157

def prepare_release(commit: true)
  release = ReleaseMigration.new(nil, branch.version).create
  submit "Prepared new release based on #{version}", commit
  release
end

#prepare_schema(name, commit: true) ⇒ Object



163
164
165
166
167
168
169
170
# File 'lib/prick/project.rb', line 163

def prepare_schema(name, commit: true)
  path = File.join(SCHEMAS_DIR, name)
  FileUtils.mkdir_p(path)
  Git.add Share.cp("schema/*", path, clobber: false, templates: { 'SCHEMA' => name })
  File.open(branch.schema.yml_file, "a") { |f| f.write("- #{name}\n") }
  Git.add(branch.schema.yml_file)
  submit "Added schema #{name}", commit
end

#restore(path = nil) ⇒ Object



153
154
155
# File 'lib/prick/project.rb', line 153

def restore(path = nil)
  load path || Dir.glob(File.join(SPOOL_DIR, "#{name}-*.sql.gz")).sort.last
end

#save(file, database: self.database) ⇒ Object



137
138
139
140
# File 'lib/prick/project.rb', line 137

def save(file, database: self.database)
  database.save(file)
  self
end

#tag?Boolean

True if we’re on a tag

Returns:

  • (Boolean)


29
# File 'lib/prick/project.rb', line 29

def tag?() Git.detached? end

#upgradeObject

TODO: Make production-only and add a to-argument



298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/prick/project.rb', line 298

def upgrade
  branches = list_upgrades(database.version, branch.version)
  FileUtils.mkdir_p(TMP_DIR)
  Dir.mktmpdir("clone-", TMP_DIR) { |dir|
    Command.command "git clone . #{dir}"
    Dir.chdir(dir) {
      branches.each { |branch|
        branch.checkout_release
        project = Project.load
        project.branch.migrate(project.database)
      }
    }
  }
end

#versionObject

Version of the current branch. If is defined as #branch.version



18
# File 'lib/prick/project.rb', line 18

def version() branch.version end