> I've actually put off doing breaking changes simply because I didn't want to bump the major version number
That's kind of the point though, right. As an API consumer/library user breaking changes are a pain in the ass. A library that rapidly issues breaking changes is one that is just plain difficult to use. Part of the goal with semantic versioning is to push authors to slow down on that and respect the commitments of their users.
Also, another thought here is that if you're hitting that many breaking changes that quickly, you possibly went 1.0 too early.
> where the first component is for "significant" releases, and the second component is what you use when you're simply introducing breaking changes
From a user's point of view, a breaking change is a significant release. It means I can't just hit update and get bug fixes (hopefully) for no effort. I have to investigate changes and test everything to make sure my software will still work.
There's a difference between a major upgrade that breaks nearly everything (like Angular to Angular2) and a smaller but necessary change in a couple of the many functions of an API. So I think there is a point for a major.breaking.minor.patch versioning, depending on the amount of work necessary when bumping major vs breaking.
Major would be for rewrites: (almost) a new product, but with the same name for brand recognition.
This seems like a nice idea on the face of it, but it's a lot less simple (who decides what's major and what isn't) and plus, when you introduce a "small" breaking change you have no idea what impact that's going to have downstream. It could be a really big deal for a client. The fact that it's a breaking change by definition means it's going to be a problem for someone and you really have no way to know how deeply entrenched that small piece of API surface is.
What bothers me about the current trends in release management is that there is way too much emphasis on iterating quickly, even for libraries where that's completely inappropriate.
It's much better to just put all your breaking changes into an unstable branch and only merge to stable (and change version numbers) infrequently, when you're sure the dust has settled. Anyone that really needs the latest updates can pull the unstable branch at their own risk, but everyone else doesn't have their build broken every other day. This is a really serious problem in the JavaScript ecosystem right now (even disregarding the left-pad debacle).
You can only do that once. Once you hit v1, if you introduce new APIs later, there's no "grace period" to make small breaking changes to those APIs.
In my case, the library that I avoided making breaking changes to because I didn't want to bump the major version was already on v2, and I didn't want to make it v3 after just a few weeks at v2.
This can be solved by adding some sort of annotation or documentation to new api's.
In RxJava [1], parts of the API are marked with @Experimental or @Beta. @Experimental provides no guarantees, while @Beta only guarantees no breakage in the same minor version.
That's kind of the point though, right. As an API consumer/library user breaking changes are a pain in the ass. A library that rapidly issues breaking changes is one that is just plain difficult to use. Part of the goal with semantic versioning is to push authors to slow down on that and respect the commitments of their users.
Also, another thought here is that if you're hitting that many breaking changes that quickly, you possibly went 1.0 too early.
> where the first component is for "significant" releases, and the second component is what you use when you're simply introducing breaking changes
From a user's point of view, a breaking change is a significant release. It means I can't just hit update and get bug fixes (hopefully) for no effort. I have to investigate changes and test everything to make sure my software will still work.