Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements pre-releases #249

Merged
merged 5 commits into from
Jan 11, 2019
Merged

Implements pre-releases #249

merged 5 commits into from
Jan 11, 2019

Conversation

ysbaddaden
Copy link
Contributor

@ysbaddaden ysbaddaden commented Jan 8, 2019

I wanted to fix the issue found by tests in #248 but actually refactored Helper::NaturalSort and Helper::Versions as a single Versions module, responsible for comparing and sorting version numbers correctly:

  • support for unbalanced version numbers (1.0.0 == 1.0 and 1.0.1 > 1.0);
  • support for pre-release version numbers;
  • fixed approximate resolver where ~> 0.1.0 matched 0.10.0.

Supporting pre-releases in install/update commands was then just extracting pre-release versions correctly, and not filtering them out unless a requirement specifically requires one.

closes #184
fixes #183

It now accepts unbalanced versions numbers, for example:

  - 1.0 == 1.0.0.0
  - 1.0 < 1.0.1
  - 1.0.2 > 1.0

The tilde operator was matching invalid version numbers, because we
only checked that the version number started with the loose
requirement, not that it matched the version number segment. For
example "~> 0.1.0" matched "0.10.0" which was invalid.

Added some tests from @straight-shoota for Helpers::Versions and
some more tests for the lower Helpers::NaturalSort module.
Helpers::NaturalSort wasn't a real natural sort algorithm, it was
sorting segments partitioned by non alphanumeric chars. There is now
a single Versions object responsible for sorting and comparing
version numbers.

Introduces support for prerelease versions, defined as a version
number that contains an ASCII letter.  They're correctly compared
using a natural sort algorithm.
A version number is considered a prerelease when it has at least one
letter. For example `1.0.alpha` and `2.0.0-pre10` are valid
prereleases for release versions `1.0` and `2.0.0` respectively.

Prereleases will never be selected until a `shard.yml` specifically
requires one. For example `version: 1.0-alpha`. The approximate
operator is recommended since it will allow upgrading to the next
prereleases and eventually to the release. For example
`version: ~> 1.0.0.pre` can be updated to `1.0.0.rc` then `1.0.0` but not
`1.1.0.beta`.
@ysbaddaden ysbaddaden added this to the v0.9.0 milestone Jan 8, 2019
@ysbaddaden ysbaddaden changed the title Implemente pre-releases Implements pre-releases Jan 8, 2019
@ysbaddaden
Copy link
Contributor Author

Also happens to fix #183 where unbalanced version numbers since 1.0 and 1.0.0 are now considered the same.

src/config.cr Show resolved Hide resolved

when />=(.+)/
ver = $1.strip
versions.select { |v| compare(v, ver) <= 0 }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor detail: If you switch the argument positions of v and ver, the comparator lines up nicely with the restriction >=.

end

def test_sort
100.times do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why so many randomized iterations? The comparisons between items always stay the same, so this doesn't provide any additional coverage.
You don't get any more complete result than iterating through the sorted list and asserting that every item compares lower than its successors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original NaturalSort had a bug where the initial order influenced the output ordering. Adding randomization catched it. 100 is a magic number, chances of not catching the issue is veeery low, and this has no impact on speed test (fast).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just has some smell to use randomized brute force specs 😄

@straight-shoota
Copy link
Member

Hm, I was already working on a patch for this 🤔 That's why I assigned #184 to myself.

Both result and key implementation are pretty similar in your approach, so 👍

@ysbaddaden
Copy link
Contributor Author

Sorry for duplicating work. I went overboard on some free time I had 🙇

@@ -146,10 +146,19 @@ module Shards
end
end

def self.prerelease?(str)
str.each_char.any?(&.ascii_letter?)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hyphens are often also used to determine pre-releases. Rubygems considers any version with a letter or hyphen as pre-release.
This obviously only makes a difference when the pre-release tag doesn't contain any letters (like 0.1-1), which is probably pretty rare.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it really? Sigh. 0.1.1-1 looks like a build number that supersedes a 0.1.1 release, it doesn't look like a pre-release to me.

Shall we really support that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rubygems doesn't look for a hyphen, only for a letter:
https://github.com/rubygems/rubygems/blob/master/lib/rubygems/version.rb#L290-L297

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at the constructor. Internally it actually replaces a hyphen with .pre.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this needs to be supported. But rubygems made extra effort to achieve this, so maybe they have a good reason?

Anyway, it's really trivial to implement here, so why not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's really trivial to implement here

Actually, it's not, because dashes and dots are equivalent, so 1.0.0-1 == 1.0.0.1. I must use the same trick than rubygems and replace - with a .pre. so 1.0.0-1 becomes 1.0.0.pre.1 to be considered a prerelease, and thus older to 1.0.0.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The replace leads to oddities, like 0.0.1-alpha > 0.0.1.beta because we parse 0.0.1-alpha as 0.0.1.pre.alpha but don't transform 0.0.1.beta.

We should probably check the partition result (dot or dash) in addition to the segment, but... I honestly, I'm tired, here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't currently support semver perfectly then we don't have to support it perfectly in this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, n.n.n-n notation is used by homebrew, where last part denotes revision, far from being pre-release.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sija This is pretty common with any package distribution. Crystal's apt and rpm packages also use this release + iteration format.
But in semver, everything following a hyphen makes a pre-release. We can't fit both. But I agree with @RX14.

So every version containing a letter is considered a pre-release.

module Shards
class VersionsTest < Minitest::Test
def test_compare
# a is older than b:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would't use older and newer here. Version comparison is not temporal.

@ysbaddaden
Copy link
Contributor Author

I added a few tests, and also ignore semver metadata. I failed to add support for prereleases made of only numbers. Still, it's better than the current one, and there shouldn't any regression since the overall algorithm is the same.

@@ -2,7 +2,15 @@

## UNRELEASED

Features:
- Experimental support for prereleases. Add a letter to a version number to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really just experimental?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean experimental as in "works on my computer, not tested in the wild".

@@ -146,10 +146,19 @@ module Shards
end
end

def self.prerelease?(str)
str.each_char.any?(&.ascii_letter?)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sija This is pretty common with any package distribution. Crystal's apt and rpm packages also use this release + iteration format.
But in semver, everything following a hyphen makes a pre-release. We can't fit both. But I agree with @RX14.

So every version containing a letter is considered a pre-release.

@ysbaddaden ysbaddaden merged commit 301c0ff into master Jan 11, 2019
@ysbaddaden ysbaddaden deleted the rework-and-fix-natural-sort branch January 11, 2019 09:08
@vladfaust
Copy link

Thanks, @ysbaddaden 👍

@ysbaddaden
Copy link
Contributor Author

You're welcome —even if it took 2 years.

f-fr pushed a commit to f-fr/shards that referenced this pull request Jan 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Error resolving pre-release version Dependencies are satisfied rarely works
5 participants