So, you've finally run into a situation where you want to upgrade one of your project's dependencies but one of your other dependencies prevents you from doing so. In the ideal world, things would just work seamlessly, and you could just grab the latest version, but sometimes life's just not like that.
Oh well. Time to fork that library!
Before we begin, we have a choice to make: is this a temporary thing, or are we never planning to return from whence we came? It's extremely unlikely that you're actually interested in maintaining your own fork of a library indefinitely, so let's work our way through a temporary parting of the ways. Life is made simpler because there's probably just a few things that are wrong or need changing and we'd be pleased to roll back to the original library once those small issues are fixed. Everything should centre on allowing us to get back to the ideal situation.
The first step when forking is to write a failing test that exposes the missing functionality. Maybe this test is just "when I new up this object, the constructor throws an exception". Maybe it's "when this method is called, the wrong value is returned" Whatever it is, encapsulate it, because once this test passes with a later version of the original library we're going to drop our fork. Write a test per feature of bug that you're planning on adding or fixing.
Until then, we're going to need the source. Check it out from the project's source repository. If you can't do that, figure out how to get the source. You're going to need it, because the next thing to do is to fix a single failing test. Not all of them. Not five of them in one go. One failing test. Create a patch between the original source and the version that causes your test to pass. Keep that patch under your source control system, next to the library. Now repeat the process for each failing test. This way, there's a patch per feature of bug.
The reason for doing this becomes clear when we upgrade the library. The process becomes:
- Check out the new version of the library source
- Run your tests. Delete the patches associated with any passing tests.
- If you have no patches to apply, delete your fork!
- Apply your patches, one at a time. Run the associated test until it passes. You may need to "freshen the patch" once you're done.
- Rinse and repeat for each failing test
Taking a step back, the whole process boils down to three steps:
- Identify what's wrong
- Automate proving that it's wrong.
- Fix the problem and maintain the delta.
As part of being a "good citizen", and to minimise the amount of time that your forked version of the library exists for, send the failing test and the fix to the project owner.
But what would we do if we want to ship with a forked library? Or what happens when your differences are too great? That, dear reader, will be what the next post is about....