diff options
Diffstat (limited to 'scripts/regenerate-schema')
-rwxr-xr-x | scripts/regenerate-schema | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/scripts/regenerate-schema b/scripts/regenerate-schema new file mode 100755 index 00000000000..cedd612f766 --- /dev/null +++ b/scripts/regenerate-schema @@ -0,0 +1,182 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +require 'open3' +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, fetch the target branch from the server. + def remote_checkout_clean_schema + return false unless project_url + return false unless target_project_url + + run %Q[git remote add target_project #{target_project_url}.git] + run %Q[git fetch target_project #{target_branch}:#{target_branch}] + + local_checkout_clean_schema + 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" + stdout_str, stderr_str, status = Open3.capture3(cmd) + puts "#{stdout_str}#{stderr_str}\e[0m" + raise("Command failed: #{stderr_str}") unless status.success? + + stdout_str + end + + ## + # Return the base commit between source and target branch. + def merge_base + @merge_base ||= run("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 source project URL from CI environment variable. + def project_url + ENV['CI_PROJECT_URL'] + end + + ## + # Return the target project URL from CI environment variable. + def target_project_url + ENV['CI_MERGE_REQUEST_PROJECT_URL'] + end + + ## + # Return whether the script is running from CI + def ci? + ENV['CI'] + end +end + +SchemaRegenerator.new.execute |