Milestones: The Secret Trick To Better Dependency Management
Milestones are a helpful but often ignored part of SCM tools like GitHub, and they could fix many issues in both open-source and private software projects.
Modern web apps are built with many dependencies. And these dependencies get updated regularly, with bugfixes and new features. As a developer, this leads me to ask myself regular questions:
- A bug in my app is caused by a dependency, and I see on GitHub that a pull request fixing that bug has been merged. Which version should I upgrade to to get that bugfix and nothing more?
- A dependency of my project has a closed issue about a bug. The issue was apparently solved by a merged PR. Am I affected by that bug?
- I need a feature that has been merged in a dependency of my project. When can I expect this new feature?
- I regularly spend time upgrading my dependencies. What is the best moment to upgrade a particular dependency?
Nothing in the issue or the pull request lets me know if I am affected by this bug, or which version I should use to avoid it. The only way to find out is to open the project Changelog and look for the PR number. This gives me the important information: the bug was fixed in @firstname.lastname@example.org. I am fortunate here because the Material UI changelog is well organized. Most other projects (including private projects we develop) don't have the same amount of detail.
One solution is always use the latest version. But that's not always possible. Sometimes, the latest release is one major version ahead of the version you're using, and upgrading would require a lot of work. Sometimes, the latest release includes other bugs that you don't want to introduce in your project. Sometimes, projects publish a new release on every change, and the changelog is impossible to follow (I'm looking at you, semantic release).
There must be another way.
When a project publishes a security advisory, it explains which versions are affected by the bug, and which versions fix the bug. For example, this security advisory for the
lodash package explains that versions >= 3.7.0 and < 4.17.19 are affected, and that version 4.17.19 fixes the bug.
If we had the same level of information for every bugfix and feature, we would be able to solve all the problems above. But that would require a tremendous amount of work in release management. That's where milestones come in.
Milestones is an issue labeling feature that GitHub and GitLab both support (support for Jira is on the way). Just like labels, you can assign a milestone to an issue or a pull request. But unlike labels, milestones have a due date and a description, and they can be closed. An issue can only have one milestone at a time.
For instance, here are the open milestones for marmelab/react-admin, the open-source project we maintain at marmelab:
Open milestones denote future releases and can have a due date. For instance, release 4.15.2 is scheduled for the end of this week, and already contains 8 pull requests.
All merged pull requests have an associated milestone, so you always know when the change was or will be published:
Milestone names and release names are the same, so you can easily find the version that contains a particular change. For example, react-admin v4.15.1 publishes the changes in the 4.15.1 milestone:
To get this result, we have a simple process that we follow for every release. We call it the "Milestone Flow".
This is the way we use milestones at Marmelab:
- Just after publishing a new bugfix release, we create a milestone for the next bugfix version. We name it after the previous version number using semver and set a due date. For instance, we published version 4.15.1 last Friday, and we created the 4.15.2 milestone the same day, with a due date set to this Friday (yes, we release on Fridays).
- We do the same for minor releases. This means that at any given time, there are two open milestones: one for the next bugfix release (e.g. 4.15.2), and one for the next minor release (e.g. 4.16.0).
- We associate most pull requests with an existing issue (e.g. using closes #xxx in the PR description). The association is bidirectional, so the issue also links to the pull request.
- Every time we merge a pull request, we set its milestone. If it's a bugfix (and merged to
master), we set it to the next bugfix release. If it's a feature (and merged to
next), we set it to the next minor release.
- At all times, the milestones page shows the next two releases, their due dates, and the list of pull requests that will be included in them.
- When the milestone due date arrives, we close the milestone and use the list of the milestone pull requests to generate the changelog (using tf/pr_log). We then tag and publish the release, using the same name as the milestone.
- Go back to step 1.
The key is to use the same identifiers for releases and milestones, even though releases concern code and milestones concern pull requests. This way, you can easily find the release that contains a particular change, and vice-versa.
This works better if you have a fixed schedule of releases. For react-admin, we publish one new bugfix release every week, and one new minor release every month (that's 315 releases so far). So we always know what the next milestone is, and when it will be published.
The "Milestone Flow" gives visibility to the stakeholders of a project, and to the developers who maintain it. It's a simple process that can be applied to any project, open-source or private. Without effort, it solves many problems related to dependency management.
I recommend you try it on your project(s), and to ask for it on the projects you use. If every project used milestones, developers would be a bit more efficient.