diff --git a/.travis.yml b/.travis.yml index 38862dc..0af306e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,13 @@ sudo: false language: ruby script: script/test +before_install: + - bundle --version 2>/dev/null | grep -q '1.7.6' && gem install bundler -v 1.11.2 || true rvm: - "1.8.7" - "1.9.3" - "2.0" - "2.1" - "2.2" + - "2.3" + - "2.4" diff --git a/Gemfile.lock b/Gemfile.lock index 6de7930..8183e50 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - version_sorter (1.1.1) + version_sorter (2.0.3) GEM remote: https://rubygems.org/ @@ -19,3 +19,6 @@ DEPENDENCIES rake-compiler test-unit (~> 2.0.4) version_sorter! + +BUNDLED WITH + 1.16.1 diff --git a/ext/version_sorter/version_sorter.c b/ext/version_sorter/version_sorter.c index 8506a31..0e760b8 100644 --- a/ext/version_sorter/version_sorter.c +++ b/ext/version_sorter/version_sorter.c @@ -15,6 +15,7 @@ #include #define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) typedef int compare_callback_t(const void *, const void *); struct version_number { @@ -56,7 +57,8 @@ compare_version_number(const struct version_number *a, int cmp = 0; if (num_a) { - cmp = (int)ca->number - (int)cb->number; + int64_t cmp64 = (int64_t)ca->number - (int64_t)cb->number; + cmp = (int)max(-1, min(1, cmp64)); } else { cmp = strchunk_cmp( a->original, &ca->string, @@ -138,7 +140,7 @@ parse_version_number(const char *string) version->comp[comp_n].string.len = offset - start; } else { version->comp[comp_n].number = number; - num_flags |= (1 << comp_n); + num_flags |= (1ull << comp_n); } comp_n++; continue; @@ -169,36 +171,68 @@ parse_version_number(const char *string) return version; } +struct sort_context { + VALUE rb_self; + VALUE rb_version_array; + compare_callback_t *cmp; + struct version_number **versions; +}; + static VALUE -rb_version_sort_1(VALUE rb_self, VALUE rb_version_array, compare_callback_t cmp) +rb_version_sort_1_cb(VALUE arg) { - struct version_number **versions; + struct sort_context *context = (struct sort_context *)arg; long length, i; VALUE *rb_version_ptr; + length = RARRAY_LEN(context->rb_version_array); + for (i = 0; i < length; ++i) { + VALUE rb_version = rb_ary_entry(context->rb_version_array, i); + context->versions[i] = parse_version_number(StringValueCStr(rb_version)); + context->versions[i]->rb_version = rb_version; + } + + qsort(context->versions, length, sizeof(struct version_number *), context->cmp); + rb_version_ptr = RARRAY_PTR(context->rb_version_array); + + for (i = 0; i < length; ++i) { + rb_version_ptr[i] = context->versions[i]->rb_version; + } + + return context->rb_version_array; +} + +static VALUE +rb_version_sort_1(VALUE rb_self, VALUE rb_version_array, compare_callback_t cmp) +{ + long length, i; + int exception; + Check_Type(rb_version_array, T_ARRAY); length = RARRAY_LEN(rb_version_array); if (!length) return rb_ary_new(); - versions = xcalloc(length, sizeof(struct version_number *)); + struct sort_context context = { + rb_self, + rb_version_array, + cmp, + xcalloc(length, sizeof(struct version_number *)), + }; + + VALUE result = rb_protect(rb_version_sort_1_cb, (VALUE)&context, &exception); for (i = 0; i < length; ++i) { - VALUE rb_version = rb_ary_entry(rb_version_array, i); - versions[i] = parse_version_number(StringValuePtr(rb_version)); - versions[i]->rb_version = rb_version; + xfree(context.versions[i]); } + xfree(context.versions); - qsort(versions, length, sizeof(struct version_number *), cmp); - rb_version_ptr = RARRAY_PTR(rb_version_array); - - for (i = 0; i < length; ++i) { - rb_version_ptr[i] = versions[i]->rb_version; - xfree(versions[i]); + if (exception) { + rb_jump_tag(exception); } - xfree(versions); - return rb_version_array; + + return result; } static VALUE diff --git a/script/release b/script/release new file mode 100755 index 0000000..0a568e3 --- /dev/null +++ b/script/release @@ -0,0 +1,27 @@ +#!/bin/bash +# Edit the version string in the gemspec, then execute this script to: +# +# 1. Commit the version change +# 2. Tag the release +# 3. Push the tag and the current branch to GitHub +# 4. Publish the gem to RubyGems +# +set -eu + +fields=( $(gem build *.gemspec | awk '/Name:|Version:/ {print $2}') ) +name="${fields[0]}" +version="${fields[1]}" +gem="${name}-${version}.gem" +[ -n "$version" ] || exit 1 +trap "rm -f '$gem'" EXIT + +bundle install +script/test + +if ! git rev-parse --verify --quiet "refs/tags/v${version}" >/dev/null; then + git commit --allow-empty -a -m "$name $version" + git tag "v${version}" +fi + +git push origin HEAD "v${version}" +gem push "$gem" diff --git a/test/version_sorter_test.rb b/test/version_sorter_test.rb index 84e0a66..5b81617 100644 --- a/test/version_sorter_test.rb +++ b/test/version_sorter_test.rb @@ -13,6 +13,10 @@ def test_sorts_versions_correctly def test_sorts_versions_like_rubygems versions = %w(1.0.9.b 1.0.9 1.0.10 2.0 3.1.4.2 1.0.9a 2.0rc2 2.0-rc1) + if !Gem.respond_to?(:rubygems_version) || Gem.rubygems_version < Gem::Version.new('2.1.0') + # Old versions of RubyGems cannot parse semver versions like `2.0-rc1` + versions.pop() + end sorted_versions = versions.sort_by { |v| Gem::Version.new(v) } assert_equal sorted_versions, VersionSorter.sort(versions) @@ -71,6 +75,12 @@ def test_rsort_bang assert_equal ["10.0", "2.0", "1.0"], versions end + def test_int_negate + a = ["0", "2147483648"] + assert_equal a, VersionSorter.sort(a) + assert_equal a.reverse, VersionSorter.rsort(a) + end + def shuffle(array) array, result = array.dup, [] result << array.delete_at(rand(array.size)) until array.size.zero? diff --git a/version_sorter.gemspec b/version_sorter.gemspec index 1a7f990..f360353 100644 --- a/version_sorter.gemspec +++ b/version_sorter.gemspec @@ -3,7 +3,7 @@ require 'rbconfig' Gem::Specification.new do |s| s.name = 'version_sorter' - s.version = '2.0.0' + s.version = '2.0.3' s.authors = ["Chris Wanstrath", "K. Adam Christensen"] s.email = 'chris@ozmm.org' s.homepage = 'https://github.com/defunkt/version_sorter'