bundler-patch 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|