rails-schema-merge-driver
A git custom merge driver that auto-resolves the most common conflict in Rails
schema files — the bumped define(version: N) line — by keeping the higher
version. Real content conflicts are left for a human.
Why
db/schema.rb is regenerated on every migration, so the version: argument at
the top of the file changes on every concurrent branch. Whenever two branches
add migrations and meet at a merge, git flags this line as a conflict — but it
isn't a real conflict: the right answer is always the higher version, which
already implies the union of both migrations.
This driver delegates to git's built-in three-way merge, then post-processes
the resulting conflict region: if the only diverging line is define(version:
N), it picks the higher version and writes the file. Anything else is left as
a normal conflict with markers in place, exiting non-zero so git knows the file
still needs human attention.
Supported formats
ActiveRecord::Schema[X.Y].define(version: N)— standard Railsdb/schema.rb.DataMigrate::Data.define(version: N)—db/data_schema.rbfrom thedata_migrategem.
Tolerates merge.conflictstyle = diff3 and zdiff3 (three-section markers).
Installation
Add to your Gemfile in the development group:
gem "rails-schema-merge-driver", group: :development, require: false
require: false is important: the gem ships only an executable, so nothing
needs to autoload at app boot. Without it, Bundler.require would either fail
looking for a hyphenated path or load lib/rails_schema_merge_driver.rb
unnecessarily.
Then bundle install. The git-merge-rails-schema executable becomes
available under bundle exec. For a system install instead:
gem install rails-schema-merge-driver
Setup (per repository)
The driver needs to be wired in two places: .gitattributes (which files it
applies to) and .git/config (the driver definition itself, which is not
versioned and must be set up by every contributor).
1. .gitattributes
db/schema.rb merge=rails-schema
db/data_schema.rb merge=rails-schema # only if you use the data_migrate gem
The second line is only needed for projects that depend on the
data_migrate gem.
2. .git/config
Each contributor's clone needs the driver registered. Either run these once:
git config --local merge.rails-schema.name 'keep newer Rails schema version'
git config --local merge.rails-schema.driver 'git-merge-rails-schema %O %A %B %L'
…or — recommended — wire it into your bin/setup so every contributor's clone
is configured automatically when they bootstrap the project. Example, adapted
from the Librario bin/setup (the project this gem
was extracted from):
def configure_git_merge_drivers
system "git config --local merge.rails-schema.name 'keep newer Rails schema version'"
system "git config --local merge.rails-schema.driver 'git-merge-rails-schema %O %A %B %L'"
end
configure_git_merge_drivers
git config --local is idempotent, so this is safe to re-run.
If you installed the gem via bundler with require: false, you may need to
prefix the driver command with bundle exec so the executable is found from
inside the project's gem environment:
git config --local merge.rails-schema.driver 'bundle exec git-merge-rails-schema %O %A %B %L'
How it works
- The driver shells out to
git merge-file --marker-size=N current base other, which performs the standard three-way text merge. - It reads the result and looks for a conflict region whose only diverging
line is a
.define(version: N)call (handling diff3/zdiff3 base sections). - If found, it replaces the region with whichever side has the higher numeric version (underscores stripped) and writes the file back.
- Exits 0 if the file is fully resolved (no remaining
<<<<<<<markers), exits 1 otherwise so git surfaces the remaining conflict to the user.
git merge-file exit codes are passed through carefully: a hard failure (nil
or negative status) aborts immediately, since silently treating it as
"resolved" would drop the other side's changes.
Development
bin/setup # bundle install
bundle exec rake # default: test + standard
bundle exec rake test
bundle exec rake standard
To experiment in an IRB session:
bin/console
To install this gem onto your local machine, run bundle exec rake install.
Releasing
Bump RailsSchemaMergeDriver::VERSION in lib/rails_schema_merge_driver/version.rb,
update CHANGELOG.md, then:
bundle exec rake release
This tags the release, pushes the tag, and publishes the gem to RubyGems.
Acknowledgements
- Originally extracted from the Librario application (a Rails-based library management system) where this driver was developed and battle-tested.
- Adapted from tpope's gist, which
proposed the original
railsschemamerge-driver idea. - See git's gitattributes(5) docs for the full custom merge-driver protocol.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/tmaier/rails-schema-merge-driver.
License
The gem is available as open source under the terms of the MIT License.