Sometimes, I find myself working across several branches helping different teammates with their stories. Often, these branches use Rails migrations to easily manage the database changes that need to be made to support the work. This means that I end up running migrations from many different branches. Because I am human, I often forget to roll those migrations back before switching branches. When a destructive migration has to happen as part of a branch, it leaves me in a situation where I have several migrations that I need to roll back at any given time. In addition, they might be interleaved with migrations that exist on the
master branch if it’s a longer-running branch.
Missing Rails migrations
When I have database migrations that I want to track across branches, I use
rails db:migrate:status to see what the status is for every migration that exists on my current branch. However, when I have run migrations that haven’t been merged into the
master branch, that leaves me with output that looks like this:
up 20180301163041 Add chocolate chips to the cookies up 20180302132931 ********** NO FILE ********** up 20180302161231 Remove the egg from the recipe up 20180306171402 Change flour to almond flour
I find myself wondering, “where did migration 201803021132931 come from? Is it something I need to roll back for my current task?” When this happens, I need a way to figure out what the missing migrations do in order to track what work I need to do when I switch contexts. To solve this problem, I created a small Git extension, called
My workflow now for tracking these migrations across branches is as follows:
- I change to the
upstream/masterbranch on Git. This gives me the context for the current state of what is running in production (n.b. I don’t actually check out
upstream/master, but I sync my local copy of
- Then, I run
g fm -lto list any migrations that have been run, but I am missing on the branch (n.b. I use a function called
git-find-migrationto fm to make this easier to type).
- For each migration that I am missing, I copy the timestamp, then run
g fm <timestamp>to see the name of the file in my Git repository.
- Once I know the name of the file, I run
git log --all -- db/migration/<migration_file>to see the context for that file.
This workflow allows me to easily make decisions to handle the migration. Sometimes, the migration is an old one that has since been pruned from the repository (not a suggested tactic, might I add!) and it’s fine. Sometimes, it’s something that I should roll back to continue my work. In that case, I check out the branch with the migration and run a
rails db:migrate:down VERSION=<timestamp> to roll it back1.
I could likely still make some improvements to this workflow. For example, I currently limit my script to searching for only a single timestamp at once. This was because I didn’t want to figure out how to write the Git query that could find more than one. Perhaps that would speed along the process when I have several migrations that I need to roll back. This happens a lot when we’re working on a multistage, backwards-compatible, destructive change.
Even with these shortcomings, the new workflow serves me well. Feel free to download the script if this sounds interesting! Do you have any techniques for managing migrations across multiple branches?
- You always make sure your migrations can be rolled back, don’t you? ↩︎