Skip to content

Instantly share code, notes, and snippets.

@knu
Last active November 30, 2018 06:35
Show Gist options
  • Save knu/ce3211fa415c8f9ce8f1f7639f7a2960 to your computer and use it in GitHub Desktop.
Save knu/ce3211fa415c8f9ce8f1f7639f7a2960 to your computer and use it in GitHub Desktop.
git-merge-structure-sql - git merge driver for db/structure.sql of Rails projects
#!/usr/bin/env ruby
#
# git-merge-structure-sql - git merge driver for db/structure.sql of Rails projects
#
# How to use:
# $ git config merge.merge-structure-sql.name 'Rails structure.sql merge driver'
# $ git config merge.merge-structure-sql.driver 'git-merge-structure-sql %A %O %B'
#
# # To enable it locally:
# $ echo 'structure.sql merge=merge-structure-sql' >> .git/info/attributes
#
# # To enable it globally:
# $ git config --global core.attributesfile '~/.config/git/attributes'
# $ echo 'structure.sql merge=merge-structure-sql' >> ~/.config/git/attributes
#
# Copyright (c) 2018 Akinori MUSHA
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
class StructureSqlMergeDriver
VARIANTS = []
module PostgreSQL
RE_VERSION = /^\('(\d+)'\)[,;]\n/
RE_VERSIONS = /^INSERT INTO "schema_migrations" \(version\) VALUES\n\K#{RE_VERSION}+/
class << self
def match?(content)
RE_VERSIONS === content
end
def merge!(*contents)
merge_versions!(*contents)
end
private
def merge_versions!(*contents)
replacement = format_versions(
contents.inject([]) { |versions, content|
versions | content[RE_VERSIONS].scan(RE_VERSION).flatten
}.sort
)
contents.each { |content|
content.sub!(RE_VERSIONS, replacement)
}
end
def format_versions(versions)
versions.map { |version| "('%s')" % version }.join(",\n") << ";\n"
end
end
VARIANTS << self
end
module MySQL
class << self
RE_DUMP_TIMESTAMP = /^-- Dump completed on \K.+$/
RE_AUTO_INCREMENT_VALUE = /^\)(?= ).*\K AUTO_INCREMENT=\d+(?=.*;$)/
RE_VERSION = /^INSERT INTO schema_migrations \(version\) VALUES \('(\d+)'\);\s+/
RE_VERSIONS = /#{RE_VERSION}+/
def match?(content)
/^-- MySQL dump / === content
end
def merge!(*contents)
merge_dump_timestamps!(*contents)
scrub_auto_increment_values!(*contents)
merge_versions!(*contents)
end
private
def merge_dump_timestamps!(*contents)
replacement = contents.inject('') { |timestamp, content|
[timestamp, *content.scan(RE_DUMP_TIMESTAMP)].max rescue ''
}
unless replacement.empty?
contents.each { |content|
content.gsub!(RE_DUMP_TIMESTAMP, replacement)
}
end
end
def scrub_auto_increment_values!(*contents)
contents.each { |content|
content.gsub!(RE_AUTO_INCREMENT_VALUE, '')
}
end
def merge_versions!(*contents)
replacement = format_versions(
contents.inject([]) { |versions, content|
versions | content.scan(RE_VERSION).flatten
}.sort
)
contents.each { |content|
content.sub!(RE_VERSIONS, replacement)
}
end
def format_versions(versions)
versions.map { |version| "INSERT INTO schema_migrations (version) VALUES ('%s');\n\n" % version }.join
end
end
VARIANTS << self
end
def main(*files)
contents = files.map { |file| File.read(file) }
sample = contents[0]
variant = VARIANTS.find { |v| v.match?(sample) } or
raise 'unsupported dump file format'
variant.merge!(*contents)
files.zip(contents) do |file, content|
File.write(file, content)
end
exec(*%W[git merge-file -q], *files)
rescue => e
STDERR.puts "#{$0}: #{e.message}"
exit 1
end
end
if $0 == __FILE__
StructureSqlMergeDriver.new.main(*ARGV)
end
@knu
Copy link
Author

knu commented Nov 30, 2018

This has been released as a gem: https://github.com/knu/git-merge-structure-sql

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