bundler-patch 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +0 -1
- data/README.md +158 -75
- data/bin/{bundle-patch → bundler-patch} +1 -1
- data/bundler-patch.gemspec +4 -4
- data/lib/bundler/patch/advisory_consolidator.rb +17 -3
- data/lib/bundler/patch/cli.rb +112 -0
- data/lib/bundler/patch/conservative_definition.rb +24 -33
- data/lib/bundler/patch/conservative_resolver.rb +24 -27
- data/lib/bundler/patch/gems_to_patch_reconciler.rb +22 -0
- data/lib/bundler/patch/version.rb +1 -1
- data/lib/bundler/patch.rb +2 -1
- metadata +15 -14
- data/lib/bundler/patch/scanner.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3c93c22ec89fe88cd7225e53b51f4367109dcc5
|
4
|
+
data.tar.gz: db75eca98eb9622a9299668d7c8d5e77868b73f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cb458c1bfb71fd70fc1fc5145caa97c391aead8dddbc8fbc292de9ea310bdc47d3b15f5b63c0dc7325e6e53a502a2be0bfc5a5a7c6921aa653407d15e2920fa
|
7
|
+
data.tar.gz: 799ce18e825ddc8027b9ac06553874e094262f0f1d23951cf863b28fc402d4f5d1eb37f95c21fecf6d668f19730cd6810242eb533fd6e5241226f4d29d486589
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# bundler-patch
|
2
2
|
|
3
|
-
`bundler-patch` can update your
|
3
|
+
`bundler-patch` can update your gems conservatively to deal with vulnerable
|
4
|
+
gems or just get more current.
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
- Don't security patch past the minimum gem version required. (This may change).
|
10
|
-
- Minimal munging to existing version spec.
|
11
|
-
- Support a database of custom advisories for internal gems.
|
12
|
-
- Provide reasonable support for keeping a large number of apps and services up-to-date as automatically as possible.
|
6
|
+
By default, "conservatively" means it will prefer the latest releases from the
|
7
|
+
current version, over the latest minor releases or the latest major releases.
|
8
|
+
This is somewhat opposite from `bundle update` which prefers newest/major
|
9
|
+
versions first.
|
13
10
|
|
14
11
|
## Installation
|
15
12
|
|
@@ -17,120 +14,206 @@
|
|
17
14
|
|
18
15
|
## Usage
|
19
16
|
|
20
|
-
|
17
|
+
With the `bundler-patch` binary available, both `bundler-patch` and `bundle
|
18
|
+
patch` can be used to execute.
|
19
|
+
|
20
|
+
Without any options, all gems will be conservatively updated. An attempt to
|
21
|
+
upgrade any vulnerable gem (according to
|
22
|
+
https://github.com/rubysec/ruby-advisory-db) to a patched version will be
|
23
|
+
made.
|
24
|
+
|
25
|
+
$ bundle patch
|
26
|
+
|
27
|
+
"Conservatively" means it will sort all available versions to prefer the
|
28
|
+
latest releases from the current version, then the latest minor releases and
|
29
|
+
then the latest major releases.
|
30
|
+
|
31
|
+
Gem requirements as defined in the Gemfile will restrict the available version
|
32
|
+
options.
|
33
|
+
|
34
|
+
For example, if gem 'foo' is locked at 1.0.2, with no gem requirement defined
|
35
|
+
in the Gemfile, and versions 1.0.3, 1.0.4, 1.1.0, 1.1.1, 2.0.0 all exist, the
|
36
|
+
default order of preference will be "1.0.4, 1.0.3, 1.0.2, 1.1.1, 1.1.0,
|
37
|
+
2.0.0".
|
38
|
+
|
39
|
+
"Prefer" means that no available versions are removed from consideration*, to
|
40
|
+
help ensure a suitable dependency graph can be reconciled. This does mean some
|
41
|
+
gems cannot be upgraded or will be upgraded to unexpected versions.
|
21
42
|
|
22
|
-
|
43
|
+
_*That's a white-lie. bundler-patch will actually remove from consideration
|
44
|
+
any versions older than the currently locked version, which `bundle update`
|
45
|
+
will not do. It's not common, but it is possible for `bundle update` to
|
46
|
+
regress a gem to an older version, if necessary to reconcile the dependency
|
47
|
+
graph._
|
23
48
|
|
24
|
-
|
49
|
+
With no gem names provided on the command line, all gems will be unlocked and
|
50
|
+
open for updating. A list of gem names can be passed to restrict to just those
|
51
|
+
gems.
|
25
52
|
|
26
|
-
|
53
|
+
$ bundle patch foo bar
|
27
54
|
|
28
|
-
|
55
|
+
* `-m/--minor_preferred` option will give preference for minor versions over
|
56
|
+
release versions.
|
29
57
|
|
30
|
-
|
58
|
+
* `-p/--prefer_minimal` option will reverse the preference order within
|
59
|
+
release, minor, major groups to just 'the next' version. In the prior
|
60
|
+
example, the order of preference changes to "1.0.3, 1.0.4, 1.0.2, 1.1.0,
|
61
|
+
1.1.1, 2.0.0"
|
31
62
|
|
32
|
-
|
63
|
+
* `-s/--strict_updates` option will actually remove from consideration
|
64
|
+
versions outside either the current release (or minor version if `-m`
|
65
|
+
specified). This increases the chances of Bundler being unable to
|
66
|
+
reconcile the dependency graph and could raise a `VersionConflict`.
|
33
67
|
|
34
|
-
|
68
|
+
`bundler-patch` will also check for vulnerabilities based on the
|
69
|
+
`ruby-advisory-db`, but also will _modify_ (if necessary) the gem requirement
|
70
|
+
in the Gemfile on vulnerable gems to ensure they can be upgraded.
|
35
71
|
|
36
|
-
|
72
|
+
* `-l/--list` option will just list vulnerable gems. No updates will be
|
73
|
+
performed.
|
37
74
|
|
38
|
-
|
75
|
+
* `-a/--advisory_db_path` option can provide the path to an additional
|
76
|
+
custom ruby-advisory-db styled directory. The path should not include the
|
77
|
+
final `gems` directory, that will be appended automatically. This can be
|
78
|
+
used for flagging necessary updates for custom/internal gems.
|
39
79
|
|
40
|
-
|
41
|
-
|
80
|
+
The rules for updating vulnerable gems are almost identical to the general
|
81
|
+
`bundler-patch` behavior described above, and abide by the same options (`-m`,
|
82
|
+
`-p`, and `-s`) though there are some tweaks to encourage getting to at least
|
83
|
+
a patched version of the gem. Keep in mind Bundler may choose unexpected
|
84
|
+
versions in order to satisfy the dependency graph.
|
42
85
|
|
43
|
-
|
86
|
+
* `-v/--vulnerable_gems_only` option will automatically restrict the gems
|
87
|
+
to update list to currently vulnerable gems. If a combination of `-v` and
|
88
|
+
a list of gem names are passed, the `-v` option is ignored in favor of
|
89
|
+
the listed gem names.
|
44
90
|
|
45
|
-
To update any gem conservatively, use the `update` command:
|
46
91
|
|
47
|
-
|
92
|
+
## Examples
|
48
93
|
|
49
|
-
|
50
|
-
`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
|
51
|
-
dependent gems need to be upgraded to a new minor or even major version, then it will do those as well, presuming the
|
52
|
-
gem requirements specified in the `Gemfile` also allow it.
|
94
|
+
### Single Gem
|
53
95
|
|
54
|
-
|
96
|
+
| Requirements| Locked | Available | Options | Result |
|
97
|
+
|-------------|---------|-----------------------------|----------|--------|
|
98
|
+
| foo | 1.4.3 | 1.4.4, 1.4.5, 1.5.0, 1.5.1 | | 1.4.5 |
|
99
|
+
| foo | 1.4.3 | 1.4.4, 1.4.5, 1.5.0, 1.5.1 | -m | 1.5.1 |
|
100
|
+
| foo | 1.4.3 | 1.4.4, 1.4.5, 1.5.0, 1.5.1 | -p | 1.4.4 |
|
101
|
+
| foo | 1.4.3 | 1.4.4, 1.4.5, 1.5.0, 1.5.1 | -m -p | 1.5.0 |
|
55
102
|
|
56
|
-
|
103
|
+
### Two Gems
|
57
104
|
|
58
|
-
|
59
|
-
due to the limitations, it will leave the requested gems at their current version.
|
105
|
+
Given the following gem specifications:
|
60
106
|
|
61
|
-
|
62
|
-
|
107
|
+
- foo 1.4.3, requires: ~> bar 2.0
|
108
|
+
- foo 1.4.4, requires: ~> bar 2.0
|
109
|
+
- foo 1.4.5, requires: ~> bar 2.1
|
110
|
+
- foo 1.5.0, requires: ~> bar 2.1
|
111
|
+
- foo 1.5.1, requires: ~> bar 3.0
|
112
|
+
- bar with versions 2.0.3, 2.0.4, 2.1.0, 2.1.1, 3.0.0
|
63
113
|
|
64
|
-
|
114
|
+
Gemfile:
|
65
115
|
|
66
|
-
|
67
|
-
not in strict mode.
|
116
|
+
gem 'foo'
|
68
117
|
|
69
|
-
|
70
|
-
available, then without `--minor_allowed` and without `--strict`, `foo` itself will only be upgraded to `1.4.8`, though
|
71
|
-
any gems further down the dependency tree could be upgraded to a new minor or major version if they have to be to use
|
72
|
-
`foo 1.4.8`.
|
118
|
+
Gemfile.lock:
|
73
119
|
|
74
|
-
|
75
|
-
|
76
|
-
|
120
|
+
foo (1.4.3)
|
121
|
+
bar (~> 2.0)
|
122
|
+
bar (2.0.3)
|
77
123
|
|
78
|
-
|
124
|
+
| # | Command Line | Result |
|
125
|
+
|---|--------------|---------------------------|
|
126
|
+
| 1 | | 'foo 1.4.5', 'bar 2.1.1' |
|
127
|
+
| 2 | foo | 'foo 1.4.4', 'bar 2.0.3' |
|
128
|
+
| 3 | -m | 'foo 1.5.1', 'bar 3.0.0' |
|
129
|
+
| 4 | -m -s | 'foo 1.5.0', 'bar 2.1.1' |
|
130
|
+
| 5 | -s | 'foo 1.4.4', 'bar 2.0.4' |
|
131
|
+
| 6 | -p | 'foo 1.4.4', 'bar 2.0.4' |
|
132
|
+
| 7 | -p -m | 'foo 1.5.0', 'bar 2.1.0' |
|
79
133
|
|
80
|
-
|
134
|
+
In case 1, `bar` is upgraded to 2.1.0, a minor version increase, because the
|
135
|
+
dependency from `foo` 1.4.5 required it.
|
81
136
|
|
82
|
-
|
137
|
+
In case 2, only `foo` is unlocked, so `bar` can only go to 1.4.4 to satisfy
|
138
|
+
the dependency from `foo`.
|
139
|
+
|
140
|
+
In case 3, `bar` goes up a whole major release, because a minor increase is
|
141
|
+
preferred now for `foo`.
|
142
|
+
|
143
|
+
In case 4, `foo` is preferred up to a 1.5.x, but 1.5.1 won't work because the
|
144
|
+
strict `-s` flag removes `bar` 3.0.0 from consideration since it's a major
|
145
|
+
increment.
|
146
|
+
|
147
|
+
In case 5, both `foo` and `bar` have any minor or major increments removed
|
148
|
+
from consideration, so the most they can move is up to 1.4.4 and 2.0.4.
|
149
|
+
|
150
|
+
In case 6, the prefer minimal switch `-p` means they only increment to the
|
151
|
+
next available release.
|
152
|
+
|
153
|
+
In case 7, the `-p` and `-m` switches allow both to move to just the next
|
154
|
+
available minor version.
|
83
155
|
|
84
156
|
|
85
157
|
### Troubleshooting
|
86
158
|
|
87
|
-
First
|
159
|
+
First, make sure the current `bundle` command itself runs to completion on its
|
160
|
+
own without any problems.
|
88
161
|
|
89
|
-
The most frequent problems with this tool involve expectations around what
|
90
|
-
|
91
|
-
|
162
|
+
The most frequent problems with this tool involve expectations around what
|
163
|
+
gems should or shouldn't be upgraded. This can quickly get complicated as even
|
164
|
+
a small dependency tree can involve many moving parts, and Bundler works hard
|
165
|
+
to find a combination that satisfies all of the dependencies and requirements.
|
92
166
|
|
93
|
-
You can get a (very verbose) look into how Bundler's resolution algorithm is
|
94
|
-
environment variable. While it can be
|
95
|
-
to
|
167
|
+
You can get a (very verbose) look into how Bundler's resolution algorithm is
|
168
|
+
working by setting the `DEBUG_RESOLVER` environment variable. While it can be
|
169
|
+
tricky to dig through, it should explain how it came to the conclusions it
|
170
|
+
came to.
|
96
171
|
|
97
|
-
Adding to the usual Bundler complexity, `bundler-patch` is injecting its own
|
98
|
-
its goals. If there's a bug
|
99
|
-
|
172
|
+
Adding to the usual Bundler complexity, `bundler-patch` is injecting its own
|
173
|
+
logic to the resolution process to achieve its goals. If there's a bug
|
174
|
+
involved, it's almost certainly in the `bundler-patch` code as Bundler has
|
175
|
+
been around a long time and has thorough testing and real world experience.
|
100
176
|
|
101
|
-
In particular, grep for 'Unwinding for conflict' to isolate some key issues
|
102
|
-
expect.
|
177
|
+
In particular, grep for 'Unwinding for conflict' to isolate some key issues
|
178
|
+
that may be preventing the outcome you expect.
|
103
179
|
|
104
|
-
`bundler-patch` can dump its own debug output, potentially helpful, with
|
180
|
+
`bundler-patch` can dump its own debug output, potentially helpful, with
|
181
|
+
`DEBUG_PATCH_RESOLVER`.
|
105
182
|
|
106
|
-
To get additional Bundler debugging output, enable the `DEBUG` env variable.
|
107
|
-
the downloading the full dependency
|
183
|
+
To get additional Bundler debugging output, enable the `DEBUG` env variable.
|
184
|
+
This will include all of the details of the downloading the full dependency
|
185
|
+
data from remote sources.
|
108
186
|
|
109
187
|
|
110
188
|
## Development
|
111
189
|
|
112
190
|
### Status
|
113
191
|
|
114
|
-
0.x versions are subject to breaking changes, there's a fair amount of
|
115
|
-
|
192
|
+
0.x versions are subject to breaking changes, there's a fair amount of
|
193
|
+
experimenting going on.
|
116
194
|
|
117
|
-
We'd love to get real world scenarios where things don't go as planned to help
|
118
|
-
believe a conservative update should
|
195
|
+
We'd love to get real world scenarios where things don't go as planned to help
|
196
|
+
flesh out varying details of what many believe a conservative update should
|
197
|
+
be.
|
119
198
|
|
120
199
|
### How To
|
121
200
|
|
122
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
123
|
-
|
201
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then,
|
202
|
+
run `rake spec` to run the tests. You can also run `bin/console` for an
|
203
|
+
interactive prompt that will allow you to experiment.
|
124
204
|
|
125
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
126
|
-
version number in `version.rb`, and then
|
127
|
-
|
205
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
206
|
+
To release a new version, update the version number in `version.rb`, and then
|
207
|
+
run `bundle exec rake release`, which will create a git tag for the version,
|
208
|
+
push git commits and tags, and push the `.gem` file to
|
209
|
+
[rubygems.org](https://rubygems.org).
|
128
210
|
|
129
211
|
## Contributing
|
130
212
|
|
131
|
-
Bug reports and pull requests are welcome on GitHub at
|
132
|
-
|
213
|
+
Bug reports and pull requests are welcome on GitHub at
|
214
|
+
https://github.com/livingsocial/bundler-patch.
|
133
215
|
|
134
216
|
## License
|
135
217
|
|
136
|
-
The gem is available as open source under the terms of the [MIT
|
218
|
+
The gem is available as open source under the terms of the [MIT
|
219
|
+
License](http://opensource.org/licenses/MIT).
|
data/bundler-patch.gemspec
CHANGED
@@ -16,14 +16,14 @@ Gem::Specification.new do |spec|
|
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
18
|
spec.bindir = 'bin'
|
19
|
-
spec.executables = ['
|
19
|
+
spec.executables = ['bundler-patch']
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
22
|
spec.add_dependency 'bundler-advise', '~> 1.0', '>= 1.0.3'
|
23
|
-
spec.add_dependency '
|
24
|
-
spec.add_dependency 'bundler', '~> 1.10'
|
23
|
+
spec.add_dependency 'slop', '~> 4.0'
|
24
|
+
spec.add_dependency 'bundler', '~> 1.10.0' # TODO: does not work with 1.11.x yet
|
25
25
|
|
26
|
-
spec.add_development_dependency 'bundler-fixture', '~> 1.
|
26
|
+
spec.add_development_dependency 'bundler-fixture', '~> 1.3'
|
27
27
|
spec.add_development_dependency 'pry'
|
28
28
|
spec.add_development_dependency 'rake', '~> 10.0'
|
29
29
|
spec.add_development_dependency 'rspec'
|
@@ -40,7 +40,7 @@ module Bundler::Patch
|
|
40
40
|
else
|
41
41
|
GemPatch.new(gem_name: up_spec.gem_name, old_version: old_version, patched_versions: up_spec.patched_versions)
|
42
42
|
end
|
43
|
-
end
|
43
|
+
end
|
44
44
|
end
|
45
45
|
|
46
46
|
private
|
@@ -61,13 +61,27 @@ module Bundler::Patch
|
|
61
61
|
end
|
62
62
|
|
63
63
|
class GemPatch
|
64
|
+
include Comparable
|
65
|
+
|
64
66
|
attr_reader :gem_name, :old_version, :new_version, :patched_versions
|
65
67
|
|
66
68
|
def initialize(gem_name:, old_version: nil, new_version: nil, patched_versions: nil)
|
67
69
|
@gem_name = gem_name
|
68
|
-
@old_version = old_version
|
69
|
-
@new_version = new_version
|
70
|
+
@old_version = Gem::Version.new(old_version) if old_version
|
71
|
+
@new_version = Gem::Version.new(new_version) if new_version
|
70
72
|
@patched_versions = patched_versions
|
71
73
|
end
|
74
|
+
|
75
|
+
def <=>(other)
|
76
|
+
self.gem_name <=> other.gem_name
|
77
|
+
end
|
78
|
+
|
79
|
+
def hash
|
80
|
+
@gem_name.hash
|
81
|
+
end
|
82
|
+
|
83
|
+
def eql?(other)
|
84
|
+
@gem_name.eql?(other.gem_name)
|
85
|
+
end
|
72
86
|
end
|
73
87
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'bundler/advise'
|
2
|
+
require 'slop'
|
3
|
+
|
4
|
+
module Bundler::Patch
|
5
|
+
class CLI
|
6
|
+
def self.execute
|
7
|
+
opts = Slop.parse do |o|
|
8
|
+
o.banner = "Bundler Patch Version #{Bundler::Patch::VERSION}\nUsage: bundle patch [options] [gems_to_update]"
|
9
|
+
o.separator ''
|
10
|
+
o.separator 'bundler-patch attempts to update gems conservatively.'
|
11
|
+
o.separator ''
|
12
|
+
o.bool '-m', '--minor_preferred', 'Prefer update to the latest minor.release version.' # TODO: change to --minor_preferred
|
13
|
+
o.bool '-p', '--prefer_minimal', 'Prefer minimal version updates over most recent release (or minor if -m used).'
|
14
|
+
o.bool '-s', '--strict_updates', 'Restrict any gem to be upgraded past most recent release (or minor if -m used).'
|
15
|
+
o.bool '-l', '--list', 'List vulnerable gems and new version target. No updates will be performed.'
|
16
|
+
o.bool '-v', '--vulnerable_gems_only', 'Only update vulnerable gems.'
|
17
|
+
o.string '-a', '--advisory_db_path', 'Optional custom advisory db path. `gems` dir will be appended to this path.'
|
18
|
+
o.on('-h', 'Show this help') { show_help(o) }
|
19
|
+
o.on('--help', 'Show README.md') { show_readme }
|
20
|
+
end
|
21
|
+
|
22
|
+
show_readme if opts.arguments.include?('help')
|
23
|
+
options = opts.to_hash
|
24
|
+
options[:gems_to_update] = opts.arguments
|
25
|
+
|
26
|
+
CLI.new.patch(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.show_help(opts)
|
30
|
+
puts opts.to_s(prefix: ' ')
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.show_readme
|
35
|
+
Kernel.exec "less '#{File.expand_path('../../../../README.md', __FILE__)}'"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@no_vulns_message = 'No known vulnerabilities to update.'
|
41
|
+
end
|
42
|
+
|
43
|
+
def patch(options={})
|
44
|
+
Bundler.ui = Bundler::UI::Shell.new
|
45
|
+
|
46
|
+
return list(options) if options[:list]
|
47
|
+
|
48
|
+
_patch(options)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def conservative_update(gem_patches, options={}, bundler_def=nil)
|
54
|
+
prep = DefinitionPrep.new(bundler_def, gem_patches, options).tap { |p| p.prep }
|
55
|
+
|
56
|
+
# update => true is very important, otherwise without any Gemfile changes, the installer
|
57
|
+
# may end up concluding everything can be resolved locally, nothing is changing,
|
58
|
+
# and then nothing is done. lib/bundler/cli/update.rb also hard-codes this.
|
59
|
+
Bundler::Installer.install(Bundler.root, prep.bundler_def, {'update' => true})
|
60
|
+
end
|
61
|
+
|
62
|
+
def list(options)
|
63
|
+
gem_patches = AdvisoryConsolidator.new(options).vulnerable_gems
|
64
|
+
|
65
|
+
if gem_patches.empty?
|
66
|
+
Bundler.ui.info @no_vulns_message
|
67
|
+
else
|
68
|
+
Bundler.ui.info '' # extra line to separate from advisory db update text
|
69
|
+
Bundler.ui.info 'Detected vulnerabilities:'
|
70
|
+
Bundler.ui.info '-------------------------'
|
71
|
+
Bundler.ui.info gem_patches.map(&:to_s).uniq.sort.join("\n")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def _patch(options)
|
76
|
+
vulnerable_patches = AdvisoryConsolidator.new(options).patch_gemfile_and_get_gem_specs_to_patch
|
77
|
+
requested_patches = (options.delete(:gems_to_update) || []).map { |gem_name| GemPatch.new(gem_name: gem_name) }
|
78
|
+
|
79
|
+
all_gem_patches = GemsToPatchReconciler.new(vulnerable_patches, requested_patches).reconciled_patches
|
80
|
+
all_gem_patches.push(*vulnerable_patches) if options[:vulnerable_gems_only] && all_gem_patches.empty?
|
81
|
+
|
82
|
+
vulnerable_patches, warnings = vulnerable_patches.partition { |gp| !gp.new_version.nil? }
|
83
|
+
|
84
|
+
unless warnings.empty?
|
85
|
+
warnings.each do |gp|
|
86
|
+
Bundler.ui.warn "* Could not attempt upgrade for #{gp.gem_name} from #{gp.old_version} to any patched versions " \
|
87
|
+
+ "#{gp.patched_versions.join(', ')}. Most often this is because a major version increment would be " \
|
88
|
+
+ "required and it's safer for a major version increase to be done manually."
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if vulnerable_patches.empty?
|
93
|
+
Bundler.ui.info @no_vulns_message
|
94
|
+
else
|
95
|
+
vulnerable_patches.each do |gp|
|
96
|
+
Bundler.ui.info "Attempting conservative update for vulnerable gem '#{gp.gem_name}': #{gp.old_version} => #{gp.new_version}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if all_gem_patches.empty?
|
101
|
+
Bundler.ui.info 'Updating all gems conservatively.'
|
102
|
+
else
|
103
|
+
Bundler.ui.info "Updating '#{all_gem_patches.map(&:gem_name).join(' ')}' conservatively."
|
104
|
+
end
|
105
|
+
conservative_update(all_gem_patches, options)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
if __FILE__ == $0
|
111
|
+
Bundler::Patch::CLI.execute
|
112
|
+
end
|
@@ -3,7 +3,7 @@ module Bundler::Patch
|
|
3
3
|
attr_accessor :gems_to_update
|
4
4
|
|
5
5
|
# pass-through options to ConservativeResolver
|
6
|
-
attr_accessor :strict, :
|
6
|
+
attr_accessor :strict, :minor_preferred, :prefer_minimal
|
7
7
|
|
8
8
|
# This copies more code than I'd like out of Bundler::Definition, but for now seems the least invasive way in.
|
9
9
|
# Backing up and intervening into the creation of a Definition instance itself involves a lot more code, a lot
|
@@ -31,7 +31,8 @@ module Bundler::Patch
|
|
31
31
|
resolver.gems_to_update = @gems_to_update
|
32
32
|
resolver.locked_specs = locked_specs
|
33
33
|
resolver.strict = @strict
|
34
|
-
resolver.
|
34
|
+
resolver.minor_preferred = @minor_preferred
|
35
|
+
resolver.prefer_minimal = @prefer_minimal
|
35
36
|
result = resolver.start(expanded_dependencies)
|
36
37
|
spec_set = Bundler::SpecSet.new(result)
|
37
38
|
|
@@ -46,7 +47,7 @@ module Bundler::Patch
|
|
46
47
|
|
47
48
|
def initialize(bundler_def, gem_patches, options)
|
48
49
|
@bundler_def = bundler_def
|
49
|
-
@gems_to_update =
|
50
|
+
@gems_to_update = GemsToPatch.new(gem_patches)
|
50
51
|
@options = options
|
51
52
|
end
|
52
53
|
|
@@ -54,41 +55,39 @@ module Bundler::Patch
|
|
54
55
|
@bundler_def ||= Bundler.definition(@gems_to_update.to_bundler_definition)
|
55
56
|
@bundler_def.extend ConservativeDefinition
|
56
57
|
@bundler_def.gems_to_update = @gems_to_update
|
57
|
-
@bundler_def.strict = @options[:
|
58
|
-
@bundler_def.
|
58
|
+
@bundler_def.strict = @options[:strict_updates]
|
59
|
+
@bundler_def.minor_preferred = @options[:minor_preferred]
|
60
|
+
@bundler_def.prefer_minimal = @options[:prefer_minimal]
|
59
61
|
fixup_empty_remotes if @gems_to_update.to_bundler_definition === true
|
60
62
|
@bundler_def
|
61
63
|
end
|
62
64
|
|
63
|
-
# This
|
64
|
-
#
|
65
|
-
#
|
66
|
-
# set in rubygems_aggregrate, it won't work.
|
65
|
+
# This came out a real-life case with sidekiq and sidekiq-pro where the sidekiq-pro gem is served from their gem
|
66
|
+
# server and depends on the open-source sidekiq gem served from rubygems.org, and when patching those, without
|
67
|
+
# the appropriate remotes being set in rubygems_aggregrate, it won't work.
|
67
68
|
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
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).
|
69
|
+
# The underlying issue in Bundler 1.10 appears to be when the Definition constructor receives `true` as the
|
70
|
+
# `unlock` parameter, then @locked_sources is initialized to empty array, and the related rubygems_aggregrate
|
71
|
+
# source instance ends up with no @remotes set in it, which I think happens during converge_sources. Without
|
72
|
+
# those set, then the index will list no gem versions in some cases. (It was complicated enough to discover this
|
73
|
+
# patch, I haven't fully worked out the flaw, though I believe I recreated the problem with plain ol `bundle
|
74
|
+
# update`).
|
79
75
|
def fixup_empty_remotes
|
76
|
+
STDERR.puts 'fixing empty remotes' if ENV['DEBUG_PATCH_RESOLVER']
|
80
77
|
b_sources = @bundler_def.send(:sources)
|
81
78
|
empty_remotes = b_sources.rubygems_sources.detect { |s| s.remotes.empty? }
|
79
|
+
STDERR.puts "empty_remotes: <#{empty_remotes}>" if ENV['DEBUG_PATCH_RESOLVER']
|
82
80
|
empty_remotes.remotes.push(*b_sources.rubygems_remotes) if empty_remotes
|
81
|
+
empty_remotes = b_sources.rubygems_sources.detect { |s| s.remotes.empty? }
|
82
|
+
STDERR.puts "empty_remotes after fixed: <#{empty_remotes}>" if ENV['DEBUG_PATCH_RESOLVER']
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
class
|
87
|
-
attr_reader :gem_patches
|
86
|
+
class GemsToPatch
|
87
|
+
attr_reader :gem_patches
|
88
88
|
|
89
|
-
def initialize(gem_patches
|
89
|
+
def initialize(gem_patches)
|
90
90
|
@gem_patches = Array(gem_patches)
|
91
|
-
@patching = options[:patching]
|
92
91
|
end
|
93
92
|
|
94
93
|
def to_bundler_definition
|
@@ -99,20 +98,12 @@ module Bundler::Patch
|
|
99
98
|
@gem_patches.map(&:gem_name)
|
100
99
|
end
|
101
100
|
|
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
101
|
def gem_patch_for(gem_name)
|
111
102
|
@gem_patches.detect { |gp| gp.gem_name == gem_name }
|
112
103
|
end
|
113
104
|
|
114
105
|
def unlocking_all?
|
115
|
-
@
|
106
|
+
@gem_patches.empty?
|
116
107
|
end
|
117
108
|
|
118
109
|
def unlocking_gem?(gem_name)
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Bundler::Patch
|
2
2
|
class ConservativeResolver < Bundler::Resolver
|
3
|
-
attr_accessor :locked_specs, :gems_to_update, :strict, :
|
3
|
+
attr_accessor :locked_specs, :gems_to_update, :strict, :minor_preferred, :prefer_minimal
|
4
4
|
|
5
5
|
def search_for(dependency)
|
6
6
|
res = super(dependency)
|
7
7
|
|
8
8
|
dep = dependency.dep unless dependency.is_a? Gem::Dependency
|
9
|
+
|
9
10
|
@conservative_search_for ||= {}
|
10
|
-
|
11
|
-
begin
|
11
|
+
res = @conservative_search_for[dep] ||= begin
|
12
12
|
gem_name = dep.name
|
13
13
|
|
14
14
|
# An Array per version returned, different entries for different platforms.
|
@@ -18,20 +18,14 @@ module Bundler::Patch
|
|
18
18
|
(@strict ?
|
19
19
|
filter_specs(res, locked_spec) :
|
20
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
|
21
|
+
STDERR.puts debug_format_result(dep, res).inspect if ENV['DEBUG_PATCH_RESOLVER']
|
33
22
|
end
|
34
23
|
end
|
24
|
+
|
25
|
+
# dup is important, in weird (large) cases Bundler will empty the result array corrupting the cache.
|
26
|
+
# Bundler itself doesn't have this problem because the super search_for does a select on its cached
|
27
|
+
# search results, effectively duping it.
|
28
|
+
res.dup
|
35
29
|
end
|
36
30
|
|
37
31
|
def debug_format_result(dep, res)
|
@@ -50,7 +44,7 @@ module Bundler::Patch
|
|
50
44
|
gsv = gem_spec.version
|
51
45
|
lsv = locked_spec.version
|
52
46
|
|
53
|
-
must_match = @
|
47
|
+
must_match = @minor_preferred ? [0] : [0, 1]
|
54
48
|
|
55
49
|
matches = must_match.map { |idx| gsv.segments[idx] == lsv.segments[idx] }
|
56
50
|
(matches.uniq == [true]) ? gsv.send(:>=, lsv) : false
|
@@ -62,6 +56,7 @@ module Bundler::Patch
|
|
62
56
|
sort_specs(res, locked_spec)
|
63
57
|
end
|
64
58
|
|
59
|
+
# reminder: sort still filters anything older than locked version
|
65
60
|
def sort_specs(specs, locked_spec)
|
66
61
|
return specs unless locked_spec
|
67
62
|
gem_name = locked_spec.name
|
@@ -72,33 +67,35 @@ module Bundler::Patch
|
|
72
67
|
filtered.sort do |a, b|
|
73
68
|
a_ver = a.first.version
|
74
69
|
b_ver = b.first.version
|
70
|
+
gem_patch = @gems_to_update.gem_patch_for(gem_name)
|
71
|
+
new_version = gem_patch ? gem_patch.new_version : nil
|
75
72
|
case
|
76
73
|
when a_ver.segments[0] != b_ver.segments[0]
|
77
74
|
b_ver <=> a_ver
|
78
|
-
when !@
|
75
|
+
when !@minor_preferred && (a_ver.segments[1] != b_ver.segments[1])
|
76
|
+
b_ver <=> a_ver
|
77
|
+
when @prefer_minimal && !@gems_to_update.unlocking_gem?(gem_name)
|
79
78
|
b_ver <=> a_ver
|
80
|
-
when @gems_to_update.
|
79
|
+
when @prefer_minimal && @gems_to_update.unlocking_gem?(gem_name) &&
|
80
|
+
(![a_ver, b_ver].include?(locked_version) &&
|
81
|
+
(!new_version || (new_version && a_ver >= new_version && b_ver >= new_version)))
|
81
82
|
b_ver <=> a_ver
|
82
83
|
else
|
83
84
|
a_ver <=> b_ver
|
84
85
|
end
|
85
86
|
end.tap do |result|
|
86
87
|
if @gems_to_update.unlocking_gem?(gem_name)
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
88
|
+
gem_patch = @gems_to_update.gem_patch_for(gem_name)
|
89
|
+
if gem_patch && gem_patch.new_version && @prefer_minimal
|
90
|
+
move_version_to_end(specs, gem_patch.new_version, result)
|
93
91
|
end
|
94
92
|
else
|
95
|
-
|
96
|
-
swap_version_to_end(specs, locked_version, result)
|
93
|
+
move_version_to_end(specs, locked_version, result)
|
97
94
|
end
|
98
95
|
end
|
99
96
|
end
|
100
97
|
|
101
|
-
def
|
98
|
+
def move_version_to_end(specs, version, result)
|
102
99
|
spec_group = specs.detect { |s| s.first.version.to_s == version.to_s }
|
103
100
|
if spec_group
|
104
101
|
result.reject! { |s| s.first.version.to_s === version.to_s }
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class GemsToPatchReconciler
|
2
|
+
attr_reader :reconciled_patches
|
3
|
+
|
4
|
+
def initialize(vulnerable_patches, requested_patches=[])
|
5
|
+
@vulnerable_patches = vulnerable_patches
|
6
|
+
@requested_patches = requested_patches
|
7
|
+
reconcile
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def reconcile
|
13
|
+
@reconciled_patches = []
|
14
|
+
unless @requested_patches.empty?
|
15
|
+
@vulnerable_patches.reject! { |gp| !@requested_patches.include?(gp) }
|
16
|
+
@reconciled_patches.push(*((@vulnerable_patches + @requested_patches).uniq))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
data/lib/bundler/patch.rb
CHANGED
@@ -11,5 +11,6 @@ require 'bundler/patch/ruby_version'
|
|
11
11
|
require 'bundler/patch/advisory_consolidator'
|
12
12
|
require 'bundler/patch/conservative_definition'
|
13
13
|
require 'bundler/patch/conservative_resolver'
|
14
|
-
require 'bundler/patch/
|
14
|
+
require 'bundler/patch/gems_to_patch_reconciler'
|
15
|
+
require 'bundler/patch/cli'
|
15
16
|
require 'bundler/patch/version'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundler-patch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- chrismo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler-advise
|
@@ -31,47 +31,47 @@ dependencies:
|
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 1.0.3
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: slop
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
|
-
- - "
|
37
|
+
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '0'
|
39
|
+
version: '4.0'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
|
-
- - "
|
44
|
+
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0'
|
46
|
+
version: '4.0'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: bundler
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
53
|
+
version: 1.10.0
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
60
|
+
version: 1.10.0
|
61
61
|
- !ruby/object:Gem::Dependency
|
62
62
|
name: bundler-fixture
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '1.
|
67
|
+
version: '1.3'
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '1.
|
74
|
+
version: '1.3'
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: pry
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,7 +118,7 @@ description:
|
|
118
118
|
email:
|
119
119
|
- chrismo@clabs.org
|
120
120
|
executables:
|
121
|
-
-
|
121
|
+
- bundler-patch
|
122
122
|
extensions: []
|
123
123
|
extra_rdoc_files: []
|
124
124
|
files:
|
@@ -130,17 +130,18 @@ files:
|
|
130
130
|
- LICENSE.txt
|
131
131
|
- README.md
|
132
132
|
- Rakefile
|
133
|
-
- bin/
|
133
|
+
- bin/bundler-patch
|
134
134
|
- bin/console
|
135
135
|
- bin/setup
|
136
136
|
- bundler-patch.gemspec
|
137
137
|
- lib/bundler/patch.rb
|
138
138
|
- lib/bundler/patch/advisory_consolidator.rb
|
139
|
+
- lib/bundler/patch/cli.rb
|
139
140
|
- lib/bundler/patch/conservative_definition.rb
|
140
141
|
- lib/bundler/patch/conservative_resolver.rb
|
141
142
|
- lib/bundler/patch/gemfile.rb
|
143
|
+
- lib/bundler/patch/gems_to_patch_reconciler.rb
|
142
144
|
- lib/bundler/patch/ruby_version.rb
|
143
|
-
- lib/bundler/patch/scanner.rb
|
144
145
|
- lib/bundler/patch/updater.rb
|
145
146
|
- lib/bundler/patch/version.rb
|
146
147
|
homepage: https://github.com/livingsocial/bundler-patch
|
@@ -1,85 +0,0 @@
|
|
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
|
-
puts gem_patches.map(&:to_s).uniq.sort.join("\n")
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
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.'
|
28
|
-
option :minor_allowed, type: :boolean, desc: 'Upgrade to the latest minor.release version.'
|
29
|
-
option :advisory_db_path, type: :string, desc: 'Optional custom advisory db path.'
|
30
|
-
desc 'Scans current directory for known vulnerabilities and attempts to patch your files to fix them.'
|
31
|
-
|
32
|
-
def patch(options={}) # TODO: Revamp the commands now that we've broadened into security specific and generic
|
33
|
-
header
|
34
|
-
|
35
|
-
gem_patches, warnings = AdvisoryConsolidator.new(options).patch_gemfile_and_get_gem_specs_to_patch
|
36
|
-
|
37
|
-
unless warnings.empty?
|
38
|
-
warnings.each do |gp|
|
39
|
-
# TODO: Bundler.ui
|
40
|
-
puts "* Could not attempt upgrade for #{gp.gem_name} from #{gp.old_version} to any patched versions " \
|
41
|
-
+ "#{gp.patched_versions.join(', ')}. Most often this is because a major version increment would be " \
|
42
|
-
+ "required and it's safer for a major version increase to be done manually."
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
if gem_patches.empty?
|
47
|
-
puts @no_vulns_message
|
48
|
-
else
|
49
|
-
gem_patches.each do |gp|
|
50
|
-
puts "Attempting #{gp.gem_name}: #{gp.old_version} => #{gp.new_version}" # TODO: Bundler.ui
|
51
|
-
end
|
52
|
-
|
53
|
-
puts "Updating '#{gem_patches.map(&:gem_name).join(' ')}' to address vulnerabilities"
|
54
|
-
conservative_update(gem_patches, options.merge(patching: true))
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
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.'
|
59
|
-
option :minor_allowed, type: :boolean, desc: 'Upgrade to the latest minor.release version.'
|
60
|
-
# TODO: be nice to support array w/o quotes like real `bundle update`
|
61
|
-
option :gems_to_update, type: :array, split: ' ', desc: 'Optional list of gems to update, in quotes, space delimited'
|
62
|
-
desc 'Update spec gems to the latest release version. Required gems could be upgraded to latest minor or major if necessary.'
|
63
|
-
config default_option: 'gems_to_update'
|
64
|
-
|
65
|
-
def update(options={}) # TODO: Revamp the commands now that we've broadened into security specific and generic
|
66
|
-
header
|
67
|
-
gem_patches = (options.delete(:gems_to_update) || []).map { |gem_name| GemPatch.new(gem_name: gem_name) }
|
68
|
-
conservative_update(gem_patches, options.merge(updating: true))
|
69
|
-
end
|
70
|
-
|
71
|
-
private
|
72
|
-
|
73
|
-
def header
|
74
|
-
puts "Bundler Patch Version #{Bundler::Patch::VERSION}"
|
75
|
-
end
|
76
|
-
|
77
|
-
def conservative_update(gem_patches, options={}, bundler_def=nil)
|
78
|
-
Bundler.ui = Bundler::UI::Shell.new
|
79
|
-
|
80
|
-
prep = DefinitionPrep.new(bundler_def, gem_patches, options).tap { |p| p.prep }
|
81
|
-
|
82
|
-
Bundler::Installer.install(Bundler.root, prep.bundler_def)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|