This is something which comes up every so often when working on Rails projects with a branching git workflow.
I’ll assume you’re rebasing a feature branch onto
The issue occurs when both
develop and your branch contain migrations,
leading to conflicts in the schema file - at least one conflict over the
‘version’ declared at the top of the file, and possibly other conflicts.
This ‘version’ number is a timestamp, and will correspond to the filename of the last migration which was added at the time that the schema was generated.
That’s the property that we need to preserve when rebasing:
The version number in the schema file needs to correspond to the migration which was generated last - i.e. the migration with the highest number at the beginning of its filename.
How not to fix it
The temptation is to manually resolve the conflicts by editing the schema.rb file. This risks getting into a state where the committed schema is different to the ‘schema that would be generated by migrating again’.
If that ever happens then at some point someone will run
rake db:migrate and
get a different schema file. When this happens it’s hard to know what the
correct way to resolve this is.
How to fix it
The robust approach is to rebuild the schema at each stage as follows:
Before rebasing, checkout (and pull!) develop and ensure that the database agrees with the version of the schema file on develop. You can do this with
rake db:migrate:status. Ideally you should see everything as ‘up’
- if anything is ‘down’ then most likely you haven’t migrated everything on
develop yet, so running
rake db:migrateshould sort this out.
if you see any lines like the following, this means that a migration which has been applied to your database doesn’t exist on develop and is presumably on one of the feature branches. Rails has no way of knowing how to rollback this migration, so you need to switch to whichever branch added that migration and rollback from there.
up 20170210155150 ********** NO FILE ***********
- if anything is ‘down’ then most likely you haven’t migrated everything on develop yet, so running
You can now switch to your branch and rebase.
At the first conflict, unstage the schema diff from your new branch:
git reset HEAD db/schema.rb
Discard the schema diff from your new branch:
git checkout db/schema.rb
Rebuild the schema:
Check the resulting schema diff against the original diff on your branch (e.g. by looking at the commit on the pull request) - it should be identical
- …with one exception: if the migration you’re adding was created
before any of the migrations on develop, then the
versionin the schema file won’t change. This is expected: that
versionshould always relate to the most recently generated migration.
- …with one exception: if the migration you’re adding was created before any of the migrations on develop, then the
That’s it! You can now continue your rebase.
To understand why the above approach works, the following insights might be useful:
- The schema file is constructed from the database, not directly from the migrations
rake db:migrateis run, checks a
schema_migrationstable in the database (which contains a list of the timestamps of migrations which have been run on this database) and compares that against the
db/migrationstable:. It then runs any migrations which haven’t been run yet, add adds the timestamps of those migrations into the
schema_migrationstable, and rebuilds the schema
rake db:migrate:statusshows the difference between
db/migrationsdirectory, but without running anything.
- Schemas in general are mostly reliably built in the same way even if some migrations end up being run out of order, because e.g new indexes are added in alphabetical order, not in migration order.