Personal Blog
How To Handle Database Schema Conflicts in Rails When Rebasing
Before rebasing, checkout and pull the master branch (or the branch we want to rebase onto).
rake db:migrate:status
Everything should be ‘up’, if not, then run migrations,
rake db:migrate
Now switch to the feature branch and rebase.
At the first conflict, unstage the schema diff from the feature branch,
git reset HEAD db/schema.rb
Discard the schema diff from the feature branch,
git checkout db/schema.rb
Rebuild the schema,
rake db:migrate
rebase --continue
Collaborating with colleagues and using branches on in a Rails project. Get annoyed of all the conflicting schema.rb files whenever someone else creates a migration?
Let’s assume we’re rebasing a feature branch onto master.
The issue occurs when both master and feature 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.
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.
The robust approach is to rebuild the schema at each stage as follows:
Before rebasing, checkout (and pull!) master and ensure that the database
agrees with the version of the schema file on remote master. We do this with rake
db:migrate:status
. Ideally everything should be ‘up’,
if anything is ‘down’ then we probably haven’t migrated everything on
local master yet, so running rake db:migrate
should sort this out.
if there are any lines with NO FILE
, like the below, it means that a migration that has
been applied to the database doesn’t exist on master and is on a feature branch.
Rails has no way of knowing how to rollback this migration,
so we need to switch to the branch that added the migration and
rollback from there.
up 20170210155150 ********** NO FILE ***********
We can now switch to the feature branch and rebase.
At the first conflict, unstage the schema diff from the feature branch,
git reset HEAD db/schema.rb
Discard the schema diff from the feature branch,
git checkout db/schema.rb
Rebuild the schema,
rake db:migrate
Check the resulting schema diff against the original diff on the feature branch it should be identical… …with one exception: if the migration was created before any of the migrations on master, then the version in the schema file won’t change. This is expected: that version should always relate to the most recently generated migration.
We can now rebase --continue
in peace!
The schema file is constructed from the database, not directly from the
migrations
When rake db:migrate
is run, checks a schema_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 the db/migrations
table:. It then runs
any migrations which haven’t been run yet, add adds the timestamps of those
migrations into the schema_migrations
table, and rebuilds the schema
rake db:migrate:status
shows the difference between schema_migrations
and the
db/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.
This post is based on this article by Duncan G M Stuart