https://github.com/tmaier/rails-schema-merge-driver
Git merge driver that auto-resolves Rails schema version conflicts in db/schema.rb.
https://github.com/tmaier/rails-schema-merge-driver
git git-merge-driver gitattributes merge-driver rails rails-schema ruby rubygem
Last synced: about 1 month ago
JSON representation
Git merge driver that auto-resolves Rails schema version conflicts in db/schema.rb.
- Host: GitHub
- URL: https://github.com/tmaier/rails-schema-merge-driver
- Owner: tmaier
- License: mit
- Created: 2026-04-26T07:51:18.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-04-26T11:21:18.000Z (about 1 month ago)
- Last Synced: 2026-04-29T16:34:31.502Z (about 1 month ago)
- Topics: git, git-merge-driver, gitattributes, merge-driver, rails, rails-schema, ruby, rubygem
- Language: Ruby
- Homepage: https://rubygems.org/gems/rails-schema-merge-driver
- Size: 47.9 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# rails-schema-merge-driver
[](https://rubygems.org/gems/rails-schema-merge-driver)
[](https://github.com/tmaier/rails-schema-merge-driver/actions/workflows/main.yml)
[](https://github.com/standardrb/standard)
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 Rails `db/schema.rb`.
- `DataMigrate::Data.define(version: N)` — `db/data_schema.rb` from the
[`data_migrate`](https://github.com/ilyakatz/data-migrate) gem.
Tolerates `merge.conflictstyle = diff3` and `zdiff3` (three-section markers).
## Installation
Add to your Gemfile in the development group:
```ruby
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:
```sh
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`
```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`](https://github.com/ilyakatz/data-migrate) gem.
### 2. `.git/config`
Each contributor's clone needs the driver registered. Either run these once:
```sh
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](https://www.librario.de) `bin/setup` (the project this gem
was extracted from):
```ruby
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:
```sh
git config --local merge.rails-schema.driver 'bundle exec git-merge-rails-schema %O %A %B %L'
```
## How it works
1. The driver shells out to `git merge-file --marker-size=N current base
other`, which performs the standard three-way text merge.
2. 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).
3. If found, it replaces the region with whichever side has the higher numeric
version (underscores stripped) and writes the file back.
4. 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
```sh
bin/setup # bundle install
bundle exec rake # default: test + standard
bundle exec rake test
bundle exec rake standard
```
To experiment in an IRB session:
```sh
bin/console
```
To install this gem onto your local machine, run `bundle exec rake install`.
## Releasing
Releases are published to RubyGems.org via [trusted publishing][tp] (OIDC, no
API keys) by `.github/workflows/release.yml`, triggered when a `v*` tag is
pushed. Steps:
1. Bump `RailsSchemaMergeDriver::VERSION` in
`lib/rails_schema_merge_driver/version.rb`, then run `bundle exec rake test` so `Gemfile.lock` (which path-references the gem itself) picks up
the new version.
2. Move the relevant entries from `[Unreleased]` to a new dated section in
`CHANGELOG.md`, following the [Keep a Changelog][kac] format.
3. Commit `version.rb`, `Gemfile.lock`, and `CHANGELOG.md`; push to `main`;
wait for CI to pass.
4. Tag and push:
```sh
git tag v0.x.0
git push origin v0.x.0
```
The workflow then:
- Authenticates to RubyGems.org via OIDC (no secrets required).
- Runs `bundle exec rake release` — which builds the gem and pushes it
(skips re-tagging because the tag already exists, per `bundler/gem_helper.rb`).
- Creates a GitHub Release at the tag with the CHANGELOG section as notes and
the `.gem` file attached as a downloadable asset.
[tp]: https://guides.rubygems.org/trusted-publishing/
[kac]: https://keepachangelog.com/en/1.1.0/
## Acknowledgements
- Originally extracted from the [Librario](https://www.librario.de)
application (a Rails-based library management system) where this driver was
developed and battle-tested.
- Adapted from [tpope's gist](https://gist.github.com/tpope/643979), which
proposed the original `railsschema` merge-driver idea.
- See [git's gitattributes(5) docs](https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver)
for the full custom merge-driver protocol.
## Contributing
Bug reports and pull requests are welcome on GitHub at
.
## License
The gem is available as open source under the terms of the
[MIT License](https://opensource.org/licenses/MIT).