Skip to content

Instantly share code, notes, and snippets.

@joelmoss
Last active October 20, 2023 09:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joelmoss/3574cef393a68a41efdbebe5110c1ad5 to your computer and use it in GitHub Desktop.
Save joelmoss/3574cef393a68a41efdbebe5110c1ad5 to your computer and use it in GitHub Desktop.
Maintain branch specific databases and switch on checkout
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
require 'yaml'
require 'digest'
require 'erb'
return if ENV['NO_DB_SWITCH'] == '1'
# Return if this is not a branch checkout.
return if ARGV[2] != '1'
def drop_existing_connections_to_database(database_name, port: 5432)
system(%[psql -p #{port} --command="SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '#{database_name}' AND pid <> pg_backend_pid();" postgres > /dev/null])
end
def branches_from_refhead(ref)
`git show-ref --heads | grep #{ref} | awk '{print $2}'`.split("\n").map do |b|
b.sub(%r{^refs/heads/}, '')
end
end
def safe_name(name) = Digest::MD5.hexdigest(name)
# Get the current (destination) branch
dest_branch = `git rev-parse --abbrev-ref HEAD`.strip
# Since we're just given a commit ID referencing the branch head we're coming from,
# it could be at the head of multiple branches. We can assume the source isn't the same as the
# destination branch, so we can remove that immediately.
source_branches = branches_from_refhead(ARGV[0]).reject { |b| b == dest_branch }
project_root = `git rev-parse --show-toplevel`.strip
# Load Rails DB config and grab database name
tpl = ERB.new(File.read("#{project_root}/config/database.yml"))
rails_db_config = YAML.load(tpl.result, aliases: true)
dev_db = rails_db_config['development']['database']
dev_db_port = rails_db_config['development'].fetch('port', 5432)
# Don't do anything if the source and destination branches are the same or nonexistent
return if source_branches.include?(dest_branch) || source_branches.empty? ||
(source_branches | [dest_branch]).any?('')
puts "Switching development database from #{source_branches.join(', ')} to #{dest_branch}..."
# Drop connections
drop_existing_connections_to_database dev_db, port: dev_db_port
# Copy dev DB to source branches
source_branches.each do |br|
sbr = safe_name(br)
print " -- Copying #{dev_db} to #{dev_db}_#{sbr} ..."
if `psql -p #{dev_db_port} -tAc "SELECT 1 FROM pg_database WHERE datname='#{dev_db}_#{sbr}'"`.strip == '1'
print ' (dropping existing one first)'
system %(dropdb -p #{dev_db_port} -f #{dev_db}_#{sbr})
end
system "createdb -p #{dev_db_port} -T #{dev_db} #{dev_db}_#{sbr}"
puts ' DONE'
end
s_dest_branch = safe_name(dest_branch)
if `psql -p #{dev_db_port} -tAc "SELECT 1 FROM pg_database WHERE datname='#{dev_db}_#{s_dest_branch}'"`.strip == '1'
print " -- Dropping #{dev_db} ..."
system %(dropdb -p #{dev_db_port} -f #{dev_db})
puts ' DONE'
# Rename destination branch DB to dev DB
print " -- Renaming #{dev_db}_#{s_dest_branch} to #{dev_db} ..."
system %(psql -p #{dev_db_port} --command="ALTER DATABASE #{dev_db}_#{s_dest_branch} RENAME TO #{dev_db};" postgres > /dev/null)
puts ' DONE'
end
# rubocop:enable Metrics/LineLength
@joelmoss
Copy link
Author

joelmoss commented Oct 6, 2023

Just call this as part of a post-checkout git hook, and it will maintain independent copies of your Postgresql database for each branch you switch to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment