mirror of
https://github.com/semver/semver.git
synced 2026-07-11 05:12:48 -05:00
Separate opinions from ordering logic in semver.org spec #69
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @isaacs on GitHub (Jun 1, 2013).
There are numerous places in the semver.org spec where it discusses whether or not a version requirement range is "satisfied by" a specific version. However, the opinions of version satisfaction vary widely between different platforms. (Consider Debian version logic, where you specify
X.y.zand might getX.y.z-rc.2+build-20130103T124356, vs npm version logic, whereX.y.zcan only ever match exactlyX.y.z, but people use comparators and ranges like>=,~>,2.3.x, etc.)Also, the specification expresses a lot of opinions for software authors, about when they SHOULD, SHOULD NOT, MUST, MUST NOT make certain changes to their version number, based on the changes to their software. While these opinions are sensible, they are somewhat subjective (eg, what sorts of changes are considered "breaking"), and make it harder for those of us writing semver parsing/comparison implementations to get at what we really care about.
In all of these cases, regardless of anyone's opinion, it's important to know very clearly which versions are greater than, less than, or equal to which other versions.
It would be very helpful if the spec broke just that logic out into a completely separate section, with explicit details, examples, and perhaps even a set of test cases. I have many for node-semver already, but they diverge from this spec somewhat – a problem I'm trying to correct. I sometimes get bug reports and pull requests for node-semver that complain about not supporting the latest stuff in the semver 2.0 rc, but there's a lot of language to wade through to find the bits I care about, and some aspects seem unclear.
Specifically, points 10 and 11 discuss this somewhat, but I'm unsure about how to deal with cases where there are multiple numeric parts in the pre-release version. For example,
1.2.3-a.2.b.55vs1.2.3-a.3.b.54.Just to be clear: the current spec as it stands is a very helpful guide for software authors, and the opinions it expresses are mostly very sane and reasonable. It's just that the logic for semver package management implementors is somewhat vague.
@isaacs commented on GitHub (Jun 1, 2013):
If you think this is a good idea, I'd be happy to write up a pull req for it.
@isaacs commented on GitHub (Jun 1, 2013):
Also, the precise meaning of "precedence" is unclear. Is this precedence in the sense of "Given available versions X, Y, and Z, all of which satisfy required version range A, Y is the best choice", or precedence in the sense of "X > Y > Z, so only Y and Z satisfy the version range
>=Y, and Z is the 'greatest' of those", or some other concept entirely?@haacked commented on GitHub (Jun 1, 2013):
Hi @isaacs,
We definitely welcome clarifications. But I guess I can't tell if what you propose is a good idea until I see it. 😄
At this point, we're on the verge of shipping 2.0. I'm a little wary of large scale changes to the spec. We've added a lot to it anyways and I'm more interested in trying to trim the fat if we can, while maintaining or improving clarity.
I'll try to address your questions here and perhaps that can help focus us on what changes are needed.
Yeah, the confusion of the term "satisfy" has been brought up before. SemVer doesn't get into specifying version ranges. That's really up to the individual platform. It's really focused on how versions are compared.
The one slight exception, of course, is the optional pre-release flag. Whether a platform supports pre-release is up to that platform. The Debian example you provided fits SemVer pre-release version matching rules. The Node example matches SemVer standard matching rules. Some platforms choose to support both via a flag.
For example, in Rails and NuGet, you have to pass a
-preflag to get the matching similar to the Debian example. Without that flag, you get the Node matching. On these platforms, it's via a flag passed to the command. I could imagine other platforms specifying these rules via comparators and other means.What are you unsure about here? I thought this was made clear here in 11.
So in your example,
1.2.3-a.2.b.55<1.2.3-a.3.b.54.The way I compare it is from left to right. The MAJOR.MINOR.PATCH versions are the same so we go to the pre-release identifier rules which means we split on the dot and for each segment apply the comparison based on the contained value. Here are the segments:
avsa<-- Equal2vs3<-- We can stop here because 2 is less than 3 and these are compared numerically.In the case of SEMVER, precedence is strictly about ordering. So I think your second example is exactly it. SemVer doesn't concern itself with any concept of "best" choice. Only how versions are compared and which ones fit in a range.
I hope that was helpful.
@isaacs commented on GitHub (Jun 1, 2013):
@Haacked Thanks, yes, this is very helpful.
Language to this effect belongs in the spec. Basically, everything you wrote here belongs in the spec, because it's very helpful in specifying how this is intended to work :) This is the kind of thing that I'd add in a pull request.
Regarding the prerelease flag, the spec also says that "numeric identifiers always have lower precedence than non-numeric identifiers". I'm not sure what that means exactly. Does it mean that 1.1.1-a.1.b.2 is lower "precedence" than 1.1.1-a.99.c.1, because c > b? Or does it mean that 1.1.1-a.b is greater than 1.1.1-a.1.b.2? What about comparing 1.1.1-a.1.b to 1.1.1-a.b.2, which one is "greater precedence"? If it's strictly a left-to-right comparison of tuples, then I suppose
1.1.1-a.1.bwould be less than1.1.1-a.b.2because1 < b, and1.1.1-a.99.c.1 > 1.1.1-a.1.b.2because99 > 1. Is this right?The specification needs a reference implementation that compares two versions, or at least pseudocode for one, and perhaps a set of test cases that any semver-compliant system should pass. These are exactly the sort of rigorous edge case examples that make specs tedious to read, but useful to code against. The current "spec" is a good manifesto, but lacks this kind of rigor.
What I'm also finding is that there are some versions in practice in npm that semver 2.0 rc calls "invalid", such as
1.1.1-2-deadbeef. This is the sort of thing output bygit describe --tags, which is used to generate version identifiers by some build tools. In this case, it's sort of like1.1.2-but-not-released-yet, so it needs to be > 1.1.1. To do this, we treatM.m.p-<number>-prereleasetagas if the<number>is a 4th numeric entry in the tuple. Probably this should be another issue, and go into semver 2.1 or something :)@haacked commented on GitHub (Jun 1, 2013):
Regarding how pre-release identifiers are compared, I think this language is in the spec except for the
left to rightpart. Perhaps that's all we need to add.Nope. Going left to right, we'd stop at
1<99and don't need to continue the comparison.Yes. Again, going left to right, when we get to the second segment we're comparing
bagainst1and since "numeric identifiers always have lower precedence than non-numeric identifiers" we conclude that1<band thus1.1.1-a.b>1.1.1-a.1.b.2.Again, I think if we just add the "left to right" comparison language that clarifies this, no? I'd love to have that language added to SemVer 2.0 if you'd like to take a cut. 😄 These are great points you bring up.
@Tieske commented on GitHub (Jun 2, 2013):
I think @Haacked is right. just adding "left to right" should do. The rigor is already in the provided examples. The one other thing missing is the comparison operators in the pre-release tag examples (that one is merely a list separated by comma instead of lt/gt)
@isaacs commented on GitHub (Jun 5, 2013):
Ok, adding "left to right" is not quite enough to specify this rigorously.
I don't get why
1.0.0-alphais <1.0.0-alpha.1. It seems like not having a prerelease version is lower precedence than having one, but then adding a number makes it HIGHER precedence?What about
1.0.0-1vs1.0.0-1.alpha?Is there a reference implementation that you can point to that does this 100% correctly?
@isaacs commented on GitHub (Jun 5, 2013):
Also, if
1.0.0-alphais less than1.0.0-alpha.1then what about comparing1.0.0-alphato1.0.0-alpha.beta?It's clear that
1.0.0-a.1is <1.0.0-a.bbecause "numeric identifiers always have lower precedence than non-numeric identifiers". But I guess what's still unclear here is how to handle a difference in the number of identifiers in the pre-release section.@isaacs commented on GitHub (Jun 6, 2013):
Just to clarify my clarification request a bit more:
But I guess what's still unclear here is how to handle a difference in the number of identifiers in the pre-release section when all the fields that ARE present in the pre-release section are otherwise equal.
@petermichaux commented on GitHub (Jun 6, 2013):
The spec already handles that case in section 11 with the following
1.0.0-alpha < 1.0.0-alpha.1
@isaacs commented on GitHub (Jun 6, 2013):
@petermichaux A single example is not a specification. That's confusing, not sufficiently specified by the language in section 11.
Where would
1.0.0-alpha..1fit into that list? Ie, how should we interpret empty or missing pre-release fields? This needs to be clarified.@isaacs commented on GitHub (Jun 6, 2013):
It seems to me that something like this would explain that ordering:
But this would mean that
1.0.0-alpha.beta > 1.0.0-alphaand that1.0.0-alpha == 1.0.0-alpha...., and I'm not sure that's the intent here.@petermichaux commented on GitHub (Jun 6, 2013):
I think section 9 needs to say that pre-release identifiers cannot be empty string. Otherwise section 11 answers the question. The examples are normative. If more explanation is needed then that won't hurt as long as there is no contradiction.
@isaacs commented on GitHub (Jun 6, 2013):
Yes, that would simplify things, certainly.
So.. what do you think? How should
1.0.0-a.bcompare to1.0.0-a?@petermichaux commented on GitHub (Jun 6, 2013):
1.0.0-a.b and 1.0.0-a is exactly analogous to 1.0.0-alpha.beta and 1.0.0-alpha which is covered in the spec.
@isaacs commented on GitHub (Jun 6, 2013):
Also regarding section 9, what does "satisfy" mean in this context? Per @Haacked's comments above, it seems like version satisfaction of a requirement range is out of scope of this specification. If that's the case, then it should be struck from section 9. If that's not the case, then "satisfy" needs to be explicitly defined.
@isaacs commented on GitHub (Jun 6, 2013):
Where?
@isaacs commented on GitHub (Jun 6, 2013):
I see
1.0.0-a.1vs1.0.0-a, but not1.0.0-a.b. Ie, is it the same for numeric and non-numeric strings, that a longer prerelease version is higher precedence than a shorter one, if all extant fields are identical?@petermichaux commented on GitHub (Jun 6, 2013):
Oops. 1.0.0-a.b and 1.0.0-a is exactly analogous to 1.0.0-alpha.1 and 1.0.0-alpha. There is no difference. The example shows that more identifiers means higher precedence. So
1.0.0-a < 1.0.0-a.b
@isaacs commented on GitHub (Jun 6, 2013):
Well, the example shows that
1.0.0-a.1is higher precedence than1.0.0-a, which isn't necessarily applicable to non-numeric identifiers. But I'll take that as the intent if no one else objects, and add it to the patch I'm working on.@petermichaux commented on GitHub (Jun 6, 2013):
If you want to get sticky, it isn't necessarily applicable to any other numeric identifiers either. I think the intent is what I mentioned and that clarification is worth it. Specific examples would be better marked as non-normative with a clear algorithm for comparison being the only normative part.
@isaacs commented on GitHub (Jun 6, 2013):
Yeah, couldn't agree more. See #103 for exactly that.
@nichtich commented on GitHub (Aug 7, 2013):
Thanks for the tests, clarifications and pseudocode. In practice, however, an implementation in basic shell tools (sh/sed/awk/sort...) would be useful.
@eins78 commented on GitHub (Mar 3, 2015):
@nichtich https://github.com/cloudflare/semver_bash
@bperrybap commented on GitHub (Apr 7, 2015):
What wasn't addressed in this thread yet was how to deal with the version strings created from git describe. (or versions that are post a given release vs before a given release).
From earlier:
This brings up an interesting point.
So far in the discussions and in the spec there is a notion of "pre-release" versions. In the case above there is a notion of "post release" development versions.
In other words so far semver seems to only be concerned with production releases and the lead up to that that known production release number. So the version extensions / meta data are related to the known upcoming version number.
And for ordering it defines that X.Y.Z is always > X.Y.Z-blah
In the case of development, there can be a need to have revisions that are post a production release but yet the actual next production release or number is not yet decided.
So when using git and using tags, if you use git describe to automatically create information for your version number, you potentially end up with something like what was above: 1.1.1-2-deadbeef where 1.1.1 was the last release and according to the rules 1.1.1-2-deadbeef would be considered earlier than 1.1.1
Ignoring the git specifics and a specific format of that version string,
(it could be reformatted using dashes and dots to be con format) it seems that under the current spec there is no way to automatically create post release versions during development without actually bumping the real version number.
This seems very unfortunate in that there is so much extensible information after the Z field and yet while there is way to specify and order releases that are leading up to a known X.Y.Z release, there is no way to indicate or order development releases that are post X.Y.Z but are not necessarily leading up to any known release number.
@eins78 commented on GitHub (Apr 7, 2015):
I did some thinking and experimenting a few weeks back regarding this issue. I haven't had time to clean up the code, so I haven't told anyone about it, but since @bperrybap brings it up…
I've built a small prototype that "parses" the output from
git semverand "always" gives you a correct semver. It is of course highly specific to my taste and needs, but I needed to get a feel for it by coding something.I came to the same conclusion as @bperrybap in that there always needs to be in bump. In fact, this is one of the few arguments of my toy CLI ("either:
--bump <major|minor|patch>or:--change <breaking|feature|bugfix>").But: I don't this is a "flaw" in the semver spec that needs to be fixed.
In the general case, we could not automatically decide between "pre release-" and "post release dev versions" anyway, because that is a decision made by the humans (even if it could be automated, then there would just be another tool setting this option). In my mind, it is just a property of the branch you're currently in: If the project starts a new major version, then a new branch is created for it and some preparation is done. Regarding semver, the minimal work I'd like to do is setting
--bump=majorsomewhere (in my case, in a CI script). I've chosenpatchas a default, because that is what I think happens once you merge the first commit after a tagged release (to a designated branch): you are pre-releasing the next path version.In a scenario where the release process is completely automated (à la grunt-semantic-release) I personally would just rely on parsing all the tags from git to find the last version, but this could also be put in build metadata if a project wants to have this.
@bhetland commented on GitHub (Apr 8, 2015):
@eins78 IIRC the "post-release" concept was formerly proposed in semver using the + delimiter instead of -, so e.g. we could have 1.2-beta but 1.2+dev. The + was at some point abandoned due to some incompatibility I think, but was there ever decided on a usable substitute for it? In our case the situation is similar to yours regarding the version numbers are only decided by humans. It seems the difference is that for a "formal" release build we explicitly control the version label, including any beta releases.
However, all other builds are just deemed "modified" w.r.t. to the current version number from the code repository, so they are just (by default) marked with our equivalent of 1.2+modified. We could conceivably also include info like a repo node#, but did not bother too much with this because the build is likely to be based on some locally modified code compared to the code from the repo. Combined with the build date this was deemed sufficient in our case, as the main idea is to identify the binary as something very informal, and a rough approximation of where in the code history it came from. I was hoping to carry on this idea pretty straightforward if/when transitioning to a semver scheme.
The other major concern we are discussing with adopting a new versioning scheme, would be to have a clear distinction of source branching reflected in the version label. At times a fairly large number of branches may be developed in parallel, so even if they all are spun off the same "base version" it is useful to distinguish between them. Sharing of concrete experiences with this would be appreciated!
@dwijnand commented on GitHub (Apr 8, 2015):
+clashes with build metadata. I proposed~(tilde) a while back: https://github.com/mojombo/semver/issues/124#issuecomment-20484470I also explored different ways to do handle post-releases without adding a "post-release" concept to semver in https://github.com/mojombo/semver/issues/124#issuecomment-20505330
@FichteFoll commented on GitHub (Apr 8, 2015):
Other related discussion(s): #200 and the issues that reference it (at the bottom)
My personal stance on this is the following: https://github.com/mojombo/semver/issues/200#issuecomment-47330860 (post-release identifier is introduced and takes over
+, metadata gets~)Btw, this appears to be a very frequent issue of the current semver spec, as well as the "generation" thing and "denoting the dependency's version".
@bperrybap commented on GitHub (Apr 8, 2015):
I saw that.
In my case, I use git and tags are mainly only used for code that is externally released but internal builds are done all the time during development and testing. There are revisions numbers that must be created for each build that get automatically generated from git information which uses the most recent tag.
I think it could go a long way for ease of use and adoption if semvar could directly use the output from git describe, when users follow some simple rules when creating their tag names.
The problem with the current way semvar versioning currently works is that it takes a one sided view that version numbers are only for outside consumption for a given release or leading up to a given known release and falls pretty flat when it comes to internal/development builds.
This makes it a hassle to deal with since you have to play artificial games with version numbers and or create unneeded tags just to create a semver compliant version string that will never see the light of day externally.
@jwdonahue commented on GitHub (Dec 2, 2017):
@isaacs, it appears as if at least some of your concerns were addressed in SemVer 2.0.0. Please close this issue at your earliest possible convenience.