bundler-patch 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +135 -0
- data/Rakefile +7 -0
- data/bin/bundle-patch +10 -0
- data/bin/console +11 -0
- data/bin/setup +7 -0
- data/bundler-patch.gemspec +30 -0
- data/lib/bundler/patch/advisory_consolidator.rb +73 -0
- data/lib/bundler/patch/conservative_definition.rb +122 -0
- data/lib/bundler/patch/conservative_resolver.rb +109 -0
- data/lib/bundler/patch/gemfile.rb +104 -0
- data/lib/bundler/patch/ruby_version.rb +25 -0
- data/lib/bundler/patch/scanner.rb +87 -0
- data/lib/bundler/patch/updater.rb +85 -0
- data/lib/bundler/patch/version.rb +5 -0
- data/lib/bundler/patch.rb +15 -0
- metadata +170 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 11622ac1ecc12f9b1357768a9d8548fa5b9d54fd
|
4
|
+
data.tar.gz: 9f824a7f9b6fe2c3478700d24281ef5006ce2102
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 272b7fef46e02020fb40ea6888501c704aaf0e93f428505d43f8b512829b0e383efef0a2e1f6aec6a08f972ee80fa826df6059cb137a3bfbe1a2524bc758e661
|
7
|
+
data.tar.gz: b71751b264518c672f6fde631f7becc3307874a9ae547437849015438d8842b74cb4548fb85f359ef3d4d1c524e1607749f88e6dd4f80f94b76f7b76f282c6d9
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.4
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 LivingSocial
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# Bundler::Patch
|
2
|
+
|
3
|
+
`bundler-patch` can update your Gemfile conservatively to deal with vulnerable gems or just get more current.
|
4
|
+
|
5
|
+
## Goals
|
6
|
+
|
7
|
+
- Update the Gemfile, .ruby-version and other files to patch an app according to `ruby-advisory-db` content.
|
8
|
+
- Don't upgrade past the minimum gem version required.
|
9
|
+
- Minimal munging to existing version spec.
|
10
|
+
- Support a database of custom advisories for internal gems.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
$ gem install bundler-patch
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
### Scan / Patch Security Vulnerable Gems
|
19
|
+
|
20
|
+
To output the list of detected vulnerabilities in the current project:
|
21
|
+
|
22
|
+
$ bundle-patch scan
|
23
|
+
|
24
|
+
To specify the path to an optional advisory database:
|
25
|
+
|
26
|
+
$ bundle-patch scan -a ~/.my-custom-db
|
27
|
+
|
28
|
+
_*NOTE*: `gems` will be appended to the end of the provided advisory database path._
|
29
|
+
|
30
|
+
To attempt to patch the detected vulnerabilities, use the `patch` command instead of `scan`:
|
31
|
+
|
32
|
+
$ bundle-patch patch
|
33
|
+
|
34
|
+
Same options apply. Read the next section for details on how bumps to release, minor and major versions work.
|
35
|
+
|
36
|
+
For help:
|
37
|
+
|
38
|
+
$ bundle-patch help scan
|
39
|
+
$ bundle-patch help patch
|
40
|
+
|
41
|
+
### Conservatively Update All Gems
|
42
|
+
|
43
|
+
To update any gem conservatively, use the `update` command:
|
44
|
+
|
45
|
+
$ bundle-patch update 'foo bar'
|
46
|
+
|
47
|
+
This will attempt to upgrade the `foo` and `bar` gems to the latest release version. (e.g. if the current version is
|
48
|
+
`1.4.3` and the latest available `1.4.x` version is `1.4.8`, it will attempt to upgrade it to `1.4.8`). If any
|
49
|
+
dependent gems need to be upgraded to a new minor or even major version, then it will do those as well, presuming the
|
50
|
+
gem requirements specified in the `Gemfile` also allow it.
|
51
|
+
|
52
|
+
If you want to restrict _any_ gem from being upgraded past the most recent release version, use `--strict` mode:
|
53
|
+
|
54
|
+
$ bundle-patch update 'foo bar' --strict
|
55
|
+
|
56
|
+
This will eliminate any newer minor or major releases for any gem. If Bundler is unable to upgrade the requested gems
|
57
|
+
due to the limitations, it will leave the requested gems at their current version.
|
58
|
+
|
59
|
+
If you want to allow minor release upgrades (e.g. to allow an upgrade from `1.4.3` to `1.6.1`) use the `--minor_allowed`
|
60
|
+
option.
|
61
|
+
|
62
|
+
`--minor_allowed` (alias `-m`) and `--strict` (alias `-s`) can be used together or independently.
|
63
|
+
|
64
|
+
While `--minor_allowed` is most useful in combination with the `--strict` option, it can also influence behavior when
|
65
|
+
not in strict mode.
|
66
|
+
|
67
|
+
For example, if an update to `foo` is requested, current version is `1.4.3` and `foo` has both `1.4.8` and `1.6.1`
|
68
|
+
available, then without `--minor_allowed` and without `--strict`, `foo` itself will only be upgraded to `1.4.8`, though
|
69
|
+
any gems further down the dependency tree could be upgraded to a new minor or major version if they have to be to use
|
70
|
+
`foo 1.4.8`.
|
71
|
+
|
72
|
+
Continuing the example, _with_ `--minor_allowed` (but still without `--strict`) `foo` itself would be upgraded to
|
73
|
+
`1.6.1`, and as before any gems further down the dependency tree could be upgraded to a new minor or major version if
|
74
|
+
they have to.
|
75
|
+
|
76
|
+
To request conservative updates for the entire Gemfile, simply call `update`:
|
77
|
+
|
78
|
+
$ bundle-patch update
|
79
|
+
|
80
|
+
There's no option to allow major version upgrades as this is the default behavior of `bundle update` in Bundler itself.
|
81
|
+
|
82
|
+
|
83
|
+
### Troubleshooting
|
84
|
+
|
85
|
+
First tip: make sure the current `bundle` command runs to completion on its own without any problems.
|
86
|
+
|
87
|
+
The most frequent problems with this tool involve expectations around what gems should or shouldn't be upgraded. This
|
88
|
+
can quickly get complicated as even a small dependency tree can involve many moving parts, and Bundler works hard to
|
89
|
+
find a combination that satisfies all of the dependencies and requirements.
|
90
|
+
|
91
|
+
You can get a (very verbose) look into how Bundler's resolution algorithm is working by setting the `DEBUG_RESOLVER`
|
92
|
+
environment variable. While it can be tricky to dig through, it should explain how it came to the conclusions it came
|
93
|
+
to.
|
94
|
+
|
95
|
+
Adding to the usual Bundler complexity, `bundler-patch` is injecting its own logic to the resolution process to achieve
|
96
|
+
its goals. If there's a bug involved, it's almost certainly in the `bundler-patch` code as Bundler has been around a
|
97
|
+
long time and has thorough testing and real world experience.
|
98
|
+
|
99
|
+
In particular, grep for 'Unwinding for conflict' to isolate some key issues that may be preventing the outcome you
|
100
|
+
expect.
|
101
|
+
|
102
|
+
`bundler-patch` can dump its own debug output, potentially helpful, with `DEBUG_PATCH_RESOLVER`.
|
103
|
+
|
104
|
+
To get additional Bundler debugging output, enable the `DEBUG` env variable. This will include all of the details of
|
105
|
+
the downloading the full dependency data from remote sources.
|
106
|
+
|
107
|
+
|
108
|
+
## Development
|
109
|
+
|
110
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can
|
111
|
+
also run `bin/console` for an interactive prompt that will allow you to experiment.
|
112
|
+
|
113
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
|
114
|
+
version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
|
115
|
+
push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
116
|
+
|
117
|
+
## Contributing
|
118
|
+
|
119
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/livingsocial/bundler-patch.
|
120
|
+
|
121
|
+
|
122
|
+
## License
|
123
|
+
|
124
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
125
|
+
|
126
|
+
|
127
|
+
## Misc
|
128
|
+
|
129
|
+
None of these do what we need, but may have some code doing some similar work in places.
|
130
|
+
|
131
|
+
- http://www.rubydoc.info/gems/bundler-auto-update/0.1.0 (runs tests after each gem upgrade)
|
132
|
+
- http://www.rubydoc.info/gems/bundler-updater/0.0.3 (interactive prompt for what's available to upgrade to)
|
133
|
+
- https://github.com/rosylilly/bundler-add (outputs Gemfile line for adding a gem)
|
134
|
+
|
135
|
+
|
data/Rakefile
ADDED
data/bin/bundle-patch
ADDED
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "bundler/patch"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
require "pry"
|
11
|
+
Pry.start
|
data/bin/setup
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bundler/patch/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'bundler-patch'
|
8
|
+
spec.version = Bundler::Patch::VERSION
|
9
|
+
spec.authors = ['chrismo']
|
10
|
+
spec.email = ['chrismo@clabs.org']
|
11
|
+
|
12
|
+
spec.summary = %q{Conservative bundler updates}
|
13
|
+
# spec.description = ''
|
14
|
+
spec.homepage = 'https://github.com/livingsocial/bundler-patch'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = 'bin'
|
19
|
+
spec.executables = ['bundle-patch']
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'bundler-advise', '~> 1.0', '>= 1.0.3'
|
23
|
+
spec.add_dependency 'boson'
|
24
|
+
spec.add_dependency 'bundler', '~> 1.10'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler-fixture', '~> 1.1'
|
27
|
+
spec.add_development_dependency 'pry'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec'
|
30
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Bundler::Patch
|
2
|
+
class AdvisoryConsolidator
|
3
|
+
def initialize(options={}, all_ads=nil)
|
4
|
+
@options = options
|
5
|
+
@all_ads = all_ads || [].tap do |a|
|
6
|
+
a << Bundler::Advise::Advisories.new unless options[:skip_bundler_advise]
|
7
|
+
a << Bundler::Advise::Advisories.new(dir: options[:advisory_db_path], repo: nil) if options[:advisory_db_path]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def vulnerable_gems
|
12
|
+
@all_ads.map do |ads|
|
13
|
+
ads.update if ads.repo
|
14
|
+
Bundler::Advise::GemAdviser.new(advisories: ads).scan_lockfile
|
15
|
+
end.flatten.map do |advisory|
|
16
|
+
patched = advisory.patched_versions.map do |pv|
|
17
|
+
# this is a little stupid for compound requirements, but works itself out in consolidate_gemfiles
|
18
|
+
pv.requirements.map { |_, v| v.to_s }
|
19
|
+
end.flatten
|
20
|
+
Gemfile.new(gem_name: advisory.gem, patched_versions: patched)
|
21
|
+
end.group_by do |gemfile|
|
22
|
+
gemfile.gem_name
|
23
|
+
end.map do |_, gemfiles|
|
24
|
+
consolidate_gemfiles(gemfiles)
|
25
|
+
end.flatten
|
26
|
+
end
|
27
|
+
|
28
|
+
def patch_gemfile_and_get_gem_specs_to_patch
|
29
|
+
gem_update_specs = vulnerable_gems
|
30
|
+
locked = Bundler::LockfileParser.new(Bundler.read_file(Bundler.default_lockfile)).specs
|
31
|
+
|
32
|
+
gem_update_specs.map(&:update) # modify requirements in Gemfile if necessary
|
33
|
+
|
34
|
+
gem_update_specs.map do |up_spec|
|
35
|
+
old_version = locked.detect { |s| s.name == up_spec.gem_name }.version.to_s
|
36
|
+
new_version = up_spec.calc_new_version(old_version)
|
37
|
+
if new_version
|
38
|
+
GemPatch.new(gem_name: up_spec.gem_name, old_version: old_version,
|
39
|
+
new_version: new_version, patched_versions: up_spec.patched_versions)
|
40
|
+
else
|
41
|
+
GemPatch.new(gem_name: up_spec.gem_name, old_version: old_version, patched_versions: up_spec.patched_versions)
|
42
|
+
end
|
43
|
+
end.partition { |gp| !gp.new_version.nil? }
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def consolidate_gemfiles(gemfiles)
|
49
|
+
gemfiles if gemfiles.length == 1
|
50
|
+
all_gem_names = gemfiles.map(&:gem_name).uniq
|
51
|
+
raise 'Must be all same gem name' unless all_gem_names.length == 1
|
52
|
+
highest_minor_patched = gemfiles.map do |g|
|
53
|
+
g.patched_versions
|
54
|
+
end.flatten.group_by do |v|
|
55
|
+
Gem::Version.new(v).segments[0..1].join('.')
|
56
|
+
end.map do |_, all|
|
57
|
+
all.sort.last
|
58
|
+
end
|
59
|
+
Gemfile.new(gem_name: all_gem_names.first, patched_versions: highest_minor_patched)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class GemPatch
|
64
|
+
attr_reader :gem_name, :old_version, :new_version, :patched_versions
|
65
|
+
|
66
|
+
def initialize(gem_name:, old_version: nil, new_version: nil, patched_versions: nil)
|
67
|
+
@gem_name = gem_name
|
68
|
+
@old_version = old_version
|
69
|
+
@new_version = new_version
|
70
|
+
@patched_versions = patched_versions
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Bundler::Patch
|
2
|
+
module ConservativeDefinition
|
3
|
+
attr_accessor :gems_to_update
|
4
|
+
|
5
|
+
# pass-through options to ConservativeResolver
|
6
|
+
attr_accessor :strict, :minor_allowed
|
7
|
+
|
8
|
+
# This copies more code than I'd like out of Bundler::Definition, but for now seems the least invasive way in.
|
9
|
+
# Backing up and intervening into the creation of a Definition instance itself involves a lot more code, a lot
|
10
|
+
# more preliminary data has to be gathered first.
|
11
|
+
def resolve
|
12
|
+
@resolve ||= begin
|
13
|
+
last_resolve = converge_locked_specs
|
14
|
+
if Bundler.settings[:frozen] || (!@unlocking && nothing_changed?)
|
15
|
+
last_resolve
|
16
|
+
else
|
17
|
+
# Run a resolve against the locally available gems
|
18
|
+
base = last_resolve.is_a?(Bundler::SpecSet) ? Bundler::SpecSet.new(last_resolve) : []
|
19
|
+
resolver = ConservativeResolver.new(index, source_requirements, base)
|
20
|
+
locked_specs = if @unlocking && @locked_specs.length == 0
|
21
|
+
# Have to grab these again. Default behavior is to not store any
|
22
|
+
# locked_specs if updating all gems, because behavior is the same
|
23
|
+
# with no lockfile OR lockfile but update them all. In our case,
|
24
|
+
# we need to know the locked versions for conservative comparison.
|
25
|
+
locked = Bundler::LockfileParser.new(@lockfile_contents)
|
26
|
+
Bundler::SpecSet.new(locked.specs)
|
27
|
+
else
|
28
|
+
@locked_specs
|
29
|
+
end
|
30
|
+
|
31
|
+
resolver.gems_to_update = @gems_to_update
|
32
|
+
resolver.locked_specs = locked_specs
|
33
|
+
resolver.strict = @strict
|
34
|
+
resolver.minor_allowed = @minor_allowed
|
35
|
+
result = resolver.start(expanded_dependencies)
|
36
|
+
spec_set = Bundler::SpecSet.new(result)
|
37
|
+
|
38
|
+
last_resolve.merge spec_set
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class DefinitionPrep
|
45
|
+
attr_reader :bundler_def
|
46
|
+
|
47
|
+
def initialize(bundler_def, gem_patches, options)
|
48
|
+
@bundler_def = bundler_def
|
49
|
+
@gems_to_update = GemsToUpdate.new(gem_patches, options)
|
50
|
+
@options = options
|
51
|
+
end
|
52
|
+
|
53
|
+
def prep
|
54
|
+
@bundler_def ||= Bundler.definition(@gems_to_update.to_bundler_definition)
|
55
|
+
@bundler_def.extend ConservativeDefinition
|
56
|
+
@bundler_def.gems_to_update = @gems_to_update
|
57
|
+
@bundler_def.strict = @options[:strict]
|
58
|
+
@bundler_def.minor_allowed = @options[:minor_allowed]
|
59
|
+
fixup_empty_remotes if @gems_to_update.to_bundler_definition === true
|
60
|
+
@bundler_def
|
61
|
+
end
|
62
|
+
|
63
|
+
# This may only matter in cases like sidekiq where the sidekiq-pro gem is served
|
64
|
+
# from their gem server and depends on the open-source sidekiq gem served from
|
65
|
+
# rubygems.org, and when patching those, without the appropriate remotes being
|
66
|
+
# set in rubygems_aggregrate, it won't work.
|
67
|
+
#
|
68
|
+
# I've seen some other weird cases where a remote source index had no entry for a
|
69
|
+
# gem and would trip up bundler-audit. I couldn't pin them down at the time though.
|
70
|
+
# But I want to keep this in case.
|
71
|
+
#
|
72
|
+
# The underlying issue in Bundler 1.10 appears to be when the Definition
|
73
|
+
# constructor receives `true` as the `unlock` parameter, then @locked_sources
|
74
|
+
# is initialized to empty array, and the related rubygems_aggregrate
|
75
|
+
# source instance ends up with no @remotes set in it, which I think happens during
|
76
|
+
# converge_sources. Without those set, then the index will list no gem versions in
|
77
|
+
# some cases. (It was complicated enough to discover this patch, I haven't fully
|
78
|
+
# worked out the flaw, which still could be on my side of the fence).
|
79
|
+
def fixup_empty_remotes
|
80
|
+
b_sources = @bundler_def.send(:sources)
|
81
|
+
empty_remotes = b_sources.rubygems_sources.detect { |s| s.remotes.empty? }
|
82
|
+
empty_remotes.remotes.push(*b_sources.rubygems_remotes) if empty_remotes
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class GemsToUpdate
|
87
|
+
attr_reader :gem_patches, :patching
|
88
|
+
|
89
|
+
def initialize(gem_patches, options={})
|
90
|
+
@gem_patches = Array(gem_patches)
|
91
|
+
@patching = options[:patching]
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_bundler_definition
|
95
|
+
unlocking_all? ? true : {gems: to_gem_names}
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_gem_names
|
99
|
+
@gem_patches.map(&:gem_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def patching_gem?(gem_name)
|
103
|
+
@patching && to_gem_names.include?(gem_name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def patching_but_not_this_gem?(gem_name)
|
107
|
+
@patching && !to_gem_names.include?(gem_name)
|
108
|
+
end
|
109
|
+
|
110
|
+
def gem_patch_for(gem_name)
|
111
|
+
@gem_patches.detect { |gp| gp.gem_name == gem_name }
|
112
|
+
end
|
113
|
+
|
114
|
+
def unlocking_all?
|
115
|
+
@patching || @gem_patches.empty?
|
116
|
+
end
|
117
|
+
|
118
|
+
def unlocking_gem?(gem_name)
|
119
|
+
unlocking_all? || to_gem_names.include?(gem_name)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Bundler::Patch
|
2
|
+
class ConservativeResolver < Bundler::Resolver
|
3
|
+
attr_accessor :locked_specs, :gems_to_update, :strict, :minor_allowed
|
4
|
+
|
5
|
+
def search_for(dependency)
|
6
|
+
res = super(dependency)
|
7
|
+
|
8
|
+
dep = dependency.dep unless dependency.is_a? Gem::Dependency
|
9
|
+
@conservative_search_for ||= {}
|
10
|
+
#@conservative_search_for[dep] ||= # TODO turning off caching allowed a real-world sample to work, dunno why yet.
|
11
|
+
begin
|
12
|
+
gem_name = dep.name
|
13
|
+
|
14
|
+
# An Array per version returned, different entries for different platforms.
|
15
|
+
# We just need the version here so it's ok to hard code this to the first instance.
|
16
|
+
locked_spec = @locked_specs[gem_name].first
|
17
|
+
|
18
|
+
(@strict ?
|
19
|
+
filter_specs(res, locked_spec) :
|
20
|
+
sort_specs(res, locked_spec)).tap do |res|
|
21
|
+
if ENV['DEBUG_PATCH_RESOLVER']
|
22
|
+
# TODO: if we keep this, gotta go through Bundler.ui
|
23
|
+
begin
|
24
|
+
if res
|
25
|
+
p debug_format_result(dep, res)
|
26
|
+
else
|
27
|
+
p "No res for #{dep.to_s}. Orig res: #{super(dependency)}"
|
28
|
+
end
|
29
|
+
rescue => e
|
30
|
+
p [e.message, e.backtrace[0..5]]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def debug_format_result(dep, res)
|
38
|
+
a = [dep.to_s,
|
39
|
+
res.map { |sg| [sg.version, sg.dependencies_for_activated_platforms.map { |dp| [dp.name, dp.requirement.to_s] }] }]
|
40
|
+
[a.first, a.last.map { |sg_data| [sg_data.first.version, sg_data.last.map { |aa| aa.join(' ') }] }]
|
41
|
+
end
|
42
|
+
|
43
|
+
def filter_specs(specs, locked_spec)
|
44
|
+
res = specs.select do |sg|
|
45
|
+
# SpecGroup is grouped by name/version, multiple entries for multiple platforms.
|
46
|
+
# We only need the name, which will be the same, so hard coding to first is ok.
|
47
|
+
gem_spec = sg.first
|
48
|
+
|
49
|
+
if locked_spec
|
50
|
+
gsv = gem_spec.version
|
51
|
+
lsv = locked_spec.version
|
52
|
+
|
53
|
+
must_match = @minor_allowed ? [0] : [0, 1]
|
54
|
+
|
55
|
+
matches = must_match.map { |idx| gsv.segments[idx] == lsv.segments[idx] }
|
56
|
+
(matches.uniq == [true]) ? gsv.send(:>=, lsv) : false
|
57
|
+
else
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
sort_specs(res, locked_spec)
|
63
|
+
end
|
64
|
+
|
65
|
+
def sort_specs(specs, locked_spec)
|
66
|
+
return specs unless locked_spec
|
67
|
+
gem_name = locked_spec.name
|
68
|
+
locked_version = locked_spec.version
|
69
|
+
|
70
|
+
filtered = specs.select { |s| s.first.version >= locked_version }
|
71
|
+
|
72
|
+
filtered.sort do |a, b|
|
73
|
+
a_ver = a.first.version
|
74
|
+
b_ver = b.first.version
|
75
|
+
case
|
76
|
+
when a_ver.segments[0] != b_ver.segments[0]
|
77
|
+
b_ver <=> a_ver
|
78
|
+
when !@minor_allowed && (a_ver.segments[1] != b_ver.segments[1])
|
79
|
+
b_ver <=> a_ver
|
80
|
+
when @gems_to_update.patching_but_not_this_gem?(gem_name)
|
81
|
+
b_ver <=> a_ver
|
82
|
+
else
|
83
|
+
a_ver <=> b_ver
|
84
|
+
end
|
85
|
+
end.tap do |result|
|
86
|
+
if @gems_to_update.unlocking_gem?(gem_name)
|
87
|
+
if @gems_to_update.patching_gem?(gem_name)
|
88
|
+
# this logic will keep a gem from updating past the patched version
|
89
|
+
# if a more recent release (or minor, if enabled) version exists.
|
90
|
+
# TODO: not sure if we want this special logic to remain or not.
|
91
|
+
new_version = @gems_to_update.gem_patch_for(gem_name).new_version
|
92
|
+
swap_version_to_end(specs, new_version, result) if new_version
|
93
|
+
end
|
94
|
+
else
|
95
|
+
# make sure the current locked version is last in list.
|
96
|
+
swap_version_to_end(specs, locked_version, result)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def swap_version_to_end(specs, version, result)
|
102
|
+
spec_group = specs.detect { |s| s.first.version.to_s == version.to_s }
|
103
|
+
if spec_group
|
104
|
+
result.reject! { |s| s.first.version.to_s === version.to_s }
|
105
|
+
result << spec_group
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Bundler::Patch
|
2
|
+
class Gemfile < UpdateSpec
|
3
|
+
attr_reader :gem_name
|
4
|
+
|
5
|
+
def initialize(target_dir: Dir.pwd,
|
6
|
+
gem_name:,
|
7
|
+
patched_versions: [])
|
8
|
+
super(target_file: 'Gemfile',
|
9
|
+
target_dir: target_dir,
|
10
|
+
patched_versions: patched_versions)
|
11
|
+
@gem_name = gem_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"#{@gem_name} #{patched_versions}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def update
|
19
|
+
# Bundler evals the whole Gemfile in Bundler::Dsl.evaluate
|
20
|
+
# It has a few magics to parse all possible calls to `gem`
|
21
|
+
# command. It doesn't have anything to output the entire
|
22
|
+
# Gemfile, I don't think it ever does that. (There is code
|
23
|
+
# to init a Gemfile from a gemspec, but it doesn't look
|
24
|
+
# like it's intended to recreate one just evaled - I don't
|
25
|
+
# see any code that would handle additional sources or
|
26
|
+
# groups - see lib/bundler/rubygems_ext.rb #to_gemfile).
|
27
|
+
#
|
28
|
+
# So without something in Bundler that round-trips from
|
29
|
+
# Gemfile back to disk and maintains integrity, then we
|
30
|
+
# couldn't re-use it to make modifications to the Gemfile
|
31
|
+
# like we'd want to, so we'll do this ourselves.
|
32
|
+
#
|
33
|
+
# We'll still instance_eval the gem line though, to properly
|
34
|
+
# handle the various options and possible multiple reqs.
|
35
|
+
@target_file = 'Gemfile'
|
36
|
+
@regexes = /^\s*gem.*['"]\s*#{@gem_name}\s*['"].*$/
|
37
|
+
file_replace do |match, re|
|
38
|
+
update_to_new_gem_version(match)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_to_new_gem_version(match)
|
43
|
+
dep = instance_eval(match)
|
44
|
+
req = dep.requirement
|
45
|
+
|
46
|
+
prefix = req.exact? ? '' : req.specific? ? '~> ' : '>= '
|
47
|
+
|
48
|
+
current_version = req.requirements.first.last.to_s
|
49
|
+
new_version = calc_new_version(current_version)
|
50
|
+
|
51
|
+
# return match if req.satisfied_by?(Gem::Version.new(new_version))
|
52
|
+
#
|
53
|
+
# TODO: This ^^ could be acceptable, slightly less complex, to not ever
|
54
|
+
# touch the requirement if it already covers the patched version.
|
55
|
+
# But I can't recall Bundler behavior in all these cases, to ensure
|
56
|
+
# at least the patch version is updated and/or we would like to be as
|
57
|
+
# conservative as possible in updating - can't recall how much influence
|
58
|
+
# we have over `bundle update` (not much)
|
59
|
+
|
60
|
+
return match if req.compound? && req.satisfied_by?(Gem::Version.new(new_version))
|
61
|
+
|
62
|
+
if new_version && prefix =~ /~/
|
63
|
+
# could Gem::Version#approximate_recommendation work here?
|
64
|
+
|
65
|
+
# match segments. if started with ~> 1.2 and new_version is 3 segments, replace with 2 segments.
|
66
|
+
count = current_version.split(/\./).length
|
67
|
+
new_version = new_version.split(/\./)[0..(count-1)].join('.')
|
68
|
+
end
|
69
|
+
|
70
|
+
if new_version
|
71
|
+
match.sub(requirements_args_regexp, " '#{prefix}#{new_version}'").tap { |s| "Updating to #{s}" }
|
72
|
+
else
|
73
|
+
match
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def requirements_args_regexp
|
80
|
+
ops = Gem::Requirement::OPS.keys.join "|"
|
81
|
+
re = /(\s*['\"]\s*(#{ops})?\s*#{Gem::Version::VERSION_PATTERN}\s*['"],*)+/
|
82
|
+
end
|
83
|
+
|
84
|
+
# See Bundler::Dsl for reference
|
85
|
+
def gem(name, *args)
|
86
|
+
# we're not concerned with options here.
|
87
|
+
_options = args.last.is_a?(Hash) ? args.pop.dup : {}
|
88
|
+
version = args || ['>= 0']
|
89
|
+
|
90
|
+
# there is a normalize_options step that DOES involve
|
91
|
+
# the args captured in version for `git` and `path`
|
92
|
+
# sources that's skipped here ... need to dig into that
|
93
|
+
# at some point.
|
94
|
+
|
95
|
+
Gem::Dependency.new(name, version)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Gem::Requirement
|
101
|
+
def compound?
|
102
|
+
@requirements.length > 1
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Bundler::Patch
|
2
|
+
class RubyVersion < UpdateSpec
|
3
|
+
def self.files
|
4
|
+
{
|
5
|
+
'.ruby-version' => [/.*/],
|
6
|
+
'.jenkins.xml' => [/\<string\>(.*)\<\/string\>/, /rvm.*\>ruby-(.*)@/, /version.*rbenv.*\>(.*)\</]
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(target_dir: Dir.pwd, patched_versions: [])
|
11
|
+
super(target_file: target_file,
|
12
|
+
target_dir: target_dir,
|
13
|
+
regexes: regexes,
|
14
|
+
patched_versions: patched_versions)
|
15
|
+
end
|
16
|
+
|
17
|
+
def update
|
18
|
+
self.class.files.each_pair do |file, regexes|
|
19
|
+
@target_file = file
|
20
|
+
@regexes = regexes
|
21
|
+
file_replace
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'bundler/advise'
|
2
|
+
require 'boson/runner'
|
3
|
+
|
4
|
+
module Bundler::Patch
|
5
|
+
class Scanner < Boson::Runner
|
6
|
+
def initialize
|
7
|
+
@no_vulns_message = 'No known vulnerabilities to update.'
|
8
|
+
end
|
9
|
+
|
10
|
+
option :advisory_db_path, type: :string, desc: 'Optional custom advisory db path.'
|
11
|
+
desc 'Scans current directory for known vulnerabilities and outputs them.'
|
12
|
+
|
13
|
+
def scan(options={}) # TODO: Revamp the commands now that we've broadened into security specific and generic
|
14
|
+
header
|
15
|
+
gem_patches = AdvisoryConsolidator.new(options).vulnerable_gems
|
16
|
+
|
17
|
+
if gem_patches.empty?
|
18
|
+
puts @no_vulns_message
|
19
|
+
else
|
20
|
+
puts # extra line to separate from advisory db update text
|
21
|
+
puts 'Detected vulnerabilities:'
|
22
|
+
puts '-------------------------'
|
23
|
+
gem_patches.each do |gp|
|
24
|
+
puts "Need to update #{gp.gem_name}: #{gp.old_version} => #{gp.new_version}" # TODO: Bundler.ui
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
option :strict, type: :boolean, desc: 'Do not allow any gem to be upgraded past most recent release (or minor if -m used). Sometimes raises VersionConflict.'
|
30
|
+
option :minor_allowed, type: :boolean, desc: 'Upgrade to the latest minor.release version.'
|
31
|
+
option :advisory_db_path, type: :string, desc: 'Optional custom advisory db path.'
|
32
|
+
desc 'Scans current directory for known vulnerabilities and attempts to patch your files to fix them.'
|
33
|
+
|
34
|
+
def patch(options={}) # TODO: Revamp the commands now that we've broadened into security specific and generic
|
35
|
+
header
|
36
|
+
|
37
|
+
gem_patches, warnings = AdvisoryConsolidator.new(options).patch_gemfile_and_get_gem_specs_to_patch
|
38
|
+
|
39
|
+
unless warnings.empty?
|
40
|
+
warnings.each do |gp|
|
41
|
+
# TODO: Bundler.ui
|
42
|
+
puts "* Could not attempt upgrade for #{gp.gem_name} from #{gp.old_version} to any patched versions " \
|
43
|
+
+ "#{gp.patched_versions.join(', ')}. Most often this is because a major version increment would be " \
|
44
|
+
+ "required and it's safer for a major version increase to be done manually."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
if gem_patches.empty?
|
49
|
+
puts @no_vulns_message
|
50
|
+
else
|
51
|
+
gem_patches.each do |gp|
|
52
|
+
puts "Attempting #{gp.gem_name}: #{gp.old_version} => #{gp.new_version}" # TODO: Bundler.ui
|
53
|
+
end
|
54
|
+
|
55
|
+
puts "Updating '#{gem_patches.map(&:gem_name).join(' ')}' to address vulnerabilities"
|
56
|
+
conservative_update(gem_patches, options.merge(patching: true))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
option :strict, type: :boolean, desc: 'Do not allow any gem to be upgraded past most recent release (or minor if -m used). Sometimes raises VersionConflict.'
|
61
|
+
option :minor_allowed, type: :boolean, desc: 'Upgrade to the latest minor.release version.'
|
62
|
+
# TODO: be nice to support array w/o quotes like real `bundle update`
|
63
|
+
option :gems_to_update, type: :array, split: ' ', desc: 'Optional list of gems to update, in quotes, space delimited'
|
64
|
+
desc 'Update spec gems to the latest release version. Required gems could be upgraded to latest minor or major if necessary.'
|
65
|
+
config default_option: 'gems_to_update'
|
66
|
+
|
67
|
+
def update(options={}) # TODO: Revamp the commands now that we've broadened into security specific and generic
|
68
|
+
header
|
69
|
+
gem_patches = (options.delete(:gems_to_update) || []).map { |gem_name| GemPatch.new(gem_name: gem_name) }
|
70
|
+
conservative_update(gem_patches, options.merge(updating: true))
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def header
|
76
|
+
puts "Bundler Patch Version #{Bundler::Patch::VERSION}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def conservative_update(gem_patches, options={}, bundler_def=nil)
|
80
|
+
Bundler.ui = Bundler::UI::Shell.new
|
81
|
+
|
82
|
+
prep = DefinitionPrep.new(bundler_def, gem_patches, options).tap { |p| p.prep }
|
83
|
+
|
84
|
+
Bundler::Installer.install(Bundler.root, prep.bundler_def)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Bundler::Patch
|
2
|
+
class UpdateSpec
|
3
|
+
attr_accessor :target_file, :target_dir, :regexes, :patched_versions
|
4
|
+
|
5
|
+
def initialize(target_file: '',
|
6
|
+
target_dir: Dir.pwd,
|
7
|
+
regexes: [/.*/],
|
8
|
+
patched_versions: [])
|
9
|
+
@target_file = target_file
|
10
|
+
@target_dir = target_dir
|
11
|
+
@regexes = regexes
|
12
|
+
@patched_versions = patched_versions
|
13
|
+
end
|
14
|
+
|
15
|
+
def target_path_fn
|
16
|
+
File.join(@target_dir, @target_file)
|
17
|
+
end
|
18
|
+
|
19
|
+
def calc_new_version(old_version)
|
20
|
+
old = old_version
|
21
|
+
all = @patched_versions.dup
|
22
|
+
return old_version if all.include?(old)
|
23
|
+
|
24
|
+
all << old
|
25
|
+
all.sort!
|
26
|
+
all.delete_if { |v| v.split(/\./).first != old.split(/\./).first } # strip non-matching major revs
|
27
|
+
all[all.index(old) + 1]
|
28
|
+
end
|
29
|
+
|
30
|
+
def file_replace
|
31
|
+
filename = target_path_fn
|
32
|
+
unless File.exist?(filename)
|
33
|
+
puts "Cannot find #{filename}"
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
guts = File.read(filename)
|
38
|
+
any_changes = false
|
39
|
+
[@regexes].flatten.each do |re|
|
40
|
+
any_changes = guts.gsub!(re) do |match|
|
41
|
+
if block_given?
|
42
|
+
yield match, re
|
43
|
+
else
|
44
|
+
update_to_new_version(match, re)
|
45
|
+
end
|
46
|
+
end || any_changes
|
47
|
+
end
|
48
|
+
|
49
|
+
if any_changes
|
50
|
+
File.open(filename, 'w') { |f| f.print guts }
|
51
|
+
verbose_puts "Updated #{filename}"
|
52
|
+
else
|
53
|
+
verbose_puts "No changes for #{filename}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def update_to_new_version(match, re)
|
58
|
+
current_version = match.scan(re).join
|
59
|
+
new_version = calc_new_version(current_version)
|
60
|
+
if new_version
|
61
|
+
match.sub(current_version, new_version).tap { |s| puts "Updating to #{s}" }
|
62
|
+
else
|
63
|
+
match
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_method :update, :file_replace
|
68
|
+
|
69
|
+
def verbose_puts(text)
|
70
|
+
puts text if @verbose
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# def prep_git_checkout(spec)
|
76
|
+
# Dir.chdir(spec.target_dir) do
|
77
|
+
# status_first_line = `git status`.split("\n").first
|
78
|
+
# raise "Not on master: #{status_first_line}" unless status_first_line == '# On branch master'
|
79
|
+
#
|
80
|
+
# raise 'Uncommitted files' unless `git status --porcelain`.chomp.empty?
|
81
|
+
#
|
82
|
+
# verbose_puts `git pull`
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
module Bundler
|
4
|
+
module Patch
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'bundler/patch/updater'
|
9
|
+
require 'bundler/patch/gemfile'
|
10
|
+
require 'bundler/patch/ruby_version'
|
11
|
+
require 'bundler/patch/advisory_consolidator'
|
12
|
+
require 'bundler/patch/conservative_definition'
|
13
|
+
require 'bundler/patch/conservative_resolver'
|
14
|
+
require 'bundler/patch/scanner'
|
15
|
+
require 'bundler/patch/version'
|
metadata
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bundler-patch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- chrismo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler-advise
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.3
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.3
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: boson
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.10'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.10'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler-fixture
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.1'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.1'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: pry
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rake
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '10.0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '10.0'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: rspec
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
description:
|
118
|
+
email:
|
119
|
+
- chrismo@clabs.org
|
120
|
+
executables:
|
121
|
+
- bundle-patch
|
122
|
+
extensions: []
|
123
|
+
extra_rdoc_files: []
|
124
|
+
files:
|
125
|
+
- ".gitignore"
|
126
|
+
- ".rspec"
|
127
|
+
- ".ruby-version"
|
128
|
+
- ".travis.yml"
|
129
|
+
- Gemfile
|
130
|
+
- LICENSE.txt
|
131
|
+
- README.md
|
132
|
+
- Rakefile
|
133
|
+
- bin/bundle-patch
|
134
|
+
- bin/console
|
135
|
+
- bin/setup
|
136
|
+
- bundler-patch.gemspec
|
137
|
+
- lib/bundler/patch.rb
|
138
|
+
- lib/bundler/patch/advisory_consolidator.rb
|
139
|
+
- lib/bundler/patch/conservative_definition.rb
|
140
|
+
- lib/bundler/patch/conservative_resolver.rb
|
141
|
+
- lib/bundler/patch/gemfile.rb
|
142
|
+
- lib/bundler/patch/ruby_version.rb
|
143
|
+
- lib/bundler/patch/scanner.rb
|
144
|
+
- lib/bundler/patch/updater.rb
|
145
|
+
- lib/bundler/patch/version.rb
|
146
|
+
homepage: https://github.com/livingsocial/bundler-patch
|
147
|
+
licenses:
|
148
|
+
- MIT
|
149
|
+
metadata: {}
|
150
|
+
post_install_message:
|
151
|
+
rdoc_options: []
|
152
|
+
require_paths:
|
153
|
+
- lib
|
154
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
requirements: []
|
165
|
+
rubyforge_project:
|
166
|
+
rubygems_version: 2.4.5.1
|
167
|
+
signing_key:
|
168
|
+
specification_version: 4
|
169
|
+
summary: Conservative bundler updates
|
170
|
+
test_files: []
|