Commit 35c09c38 authored by Toon Claes's avatar Toon Claes

Add script to regenerate schema for current branch

Users can now use `scripts/regenerate-schema` to create a clean
`db/structure.sql` for their branch.

What the script does:
1. Disable the migrations (move them where Rails would ignore them)
1. Check out out a clean `db/structure.sql`
1. Reset the test database to this schema
1. Enable the migrations again
1. Run the migrations on the test database

This recreates `db/structure.sql` with the migrations added in the
current branch.

There are several methods the script uses to check out the schema file
from the target branch:

- When `CI_MERGE_REQUEST_TARGET_BRANCH_SHA` is set, it assumes it's
  running from CI and it will `curl` the file from `CI_PROJECT_URL`
  because it assumes a shallow clone is in place.
- You can set `TARGET`, like `TARGET=stable scripts/regenerate-schema`
  to set the target branch. In this case `git checkout $TARGET` is
  used to checkout `db/structure.sql`.
- Otherwise `master` is assumed and used to `git checkout`.
parent 0138be44
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'net/http'
require 'uri'
class SchemaRegenerator
##
# Filename of the schema
#
# This file is being regenerated by this script.
FILENAME = 'db/structure.sql'
##
# Directories where migrations are stored
#
# The methods +hide_migrations+ and +unhide_migrations+ will rename
# these to disable/enable migrations.
MIGRATION_DIRS = %w[db/migrate db/post_migrate].freeze
def execute
Dir.chdir(File.expand_path('..', __dir__)) do
checkout_ref
checkout_clean_schema
hide_migrations
reset_db
unhide_migrations
migrate
ensure
unhide_migrations
end
end
private
##
# Git checkout +CI_COMMIT_SHA+.
#
# When running from CI, checkout the clean commit,
# not the merged result.
def checkout_ref
return unless ci?
run %Q[git checkout #{source_ref}]
run %q[git clean -f -- db]
end
##
# Checkout the clean schema from the target branch
def checkout_clean_schema
remote_checkout_clean_schema || local_checkout_clean_schema
end
##
# Get clean schema from remote servers
#
# This script might run in CI, using a shallow clone, so to checkout
# the file, download it from the server.
def remote_checkout_clean_schema
return false unless project_url
uri = URI.join("#{project_url}/", 'raw/', "#{merge_base}/", FILENAME)
download_schema(uri)
end
##
# Download the schema from the given +uri+.
def download_schema(uri)
puts "Downloading #{uri}..."
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
request = Net::HTTP::Get.new(uri.request_uri)
http.read_timeout = 500
http.request(request) do |response|
raise("Failed to download file: #{response.code} #{response.message}") if response.code.to_i != 200
File.open(FILENAME, 'w') do |io|
response.read_body do |chunk|
io.write(chunk)
end
end
end
end
true
end
##
# Git checkout the schema from target branch.
#
# Ask git to checkout the schema from the target branch and reset
# the file to unstage the changes.
def local_checkout_clean_schema
run %Q[git checkout #{merge_base} -- #{FILENAME}]
run %Q[git reset -- #{FILENAME}]
end
##
# Move migrations to where Rails will not find them.
#
# To reset the database to clean schema defined in +FILENAME+, move
# the migrations to a path where Rails will not find them, otherwise
# +db:reset+ would abort. Later when the migrations should be
# applied, use +unhide_migrations+ to bring them back.
def hide_migrations
MIGRATION_DIRS.each do |dir|
File.rename(dir, "#{dir}__")
end
end
##
# Undo the effect of +hide_migrations+.
#
# Place back the migrations which might be moved by
# +hide_migrations+.
def unhide_migrations
error = nil
MIGRATION_DIRS.each do |dir|
File.rename("#{dir}__", dir)
rescue Errno::ENOENT
nil
rescue StandardError => e
# Save error for later, but continue with other dirs first
error = e
end
raise error if error
end
##
# Run rake task to reset the database.
def reset_db
run %q[bin/rails db:reset RAILS_ENV=test]
end
##
# Run rake task to run migrations.
def migrate
run %q[bin/rails db:migrate RAILS_ENV=test]
end
##
# Run the given +cmd+.
#
# The command is colored green, and the output of the command is
# colored gray.
# When the command failed an exception is raised.
def run(cmd)
puts "\e[32m$ #{cmd}\e[37m"
ret = system(cmd)
puts "\e[0m"
raise("Command failed") unless ret
end
##
# Return the base commit between source and target branch.
def merge_base
@merge_base ||= `git merge-base #{target_branch} #{source_ref}`.chomp
end
##
# Return the name of the target branch
#
# Get source ref from CI environment variable, or read the +TARGET+
# environment+ variable, or default to +HEAD+.
def target_branch
ENV['CI_MERGE_REQUEST_TARGET_BRANCH_NAME'] || ENV['TARGET'] || 'master'
end
##
# Return the source ref
#
# Get source ref from CI environment variable, or default to +HEAD+.
def source_ref
ENV['CI_COMMIT_SHA'] || 'HEAD'
end
##
# Return the project URL from CI environment variable.
def project_url
ENV['CI_PROJECT_URL']
end
##
# Return whether the script is running from CI
def ci?
ENV['CI']
end
end
SchemaRegenerator.new.execute
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment