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 develop
.
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:migrate
should 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:
rake db:migrate
-
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
version
in the schema file won’t change. This is expected: thatversion
should 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.
Background
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
- When
rake db:migrate
is run, checks aschema_migrations
table in the database (which contains a list of the timestamps of migrations which have been run on this database) and compares that against thedb/migrations
table:. It then runs any migrations which haven’t been run yet, add adds the timestamps of those migrations into theschema_migrations
table, and rebuilds the schema rake db:migrate:status
shows the difference betweenschema_migrations
and thedb/migrations
directory, 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.