Skip to content

Commit

Permalink
Clean up corrupted lockfiles
Browse files Browse the repository at this point in the history
Bundler 1.1.pre.5 introduced a bug that could
introduce duplicate GIT sections, which then
caused duplicate copies of gems in the GIT
sections.

This commit makes the LockfileParser aware of the
bug, and has it clean up any corrupted lockfiles.
  • Loading branch information
tomhuda committed Mar 7, 2012
1 parent 6d4962d commit e31a1c7
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 1 deletion.
5 changes: 5 additions & 0 deletions lib/bundler/lazy_specification.rb
Expand Up @@ -25,6 +25,11 @@ def full_name
end
end

def ==(other)
[name, version, dependencies, platform, source] ==
[other.name, other.version, other.dependencies, other.platform, other.source]
end

def satisfies?(dependency)
@name == dependency.name && dependency.requirement.satisfied_by?(Gem::Version.new(@version))
end
Expand Down
21 changes: 20 additions & 1 deletion lib/bundler/lockfile_parser.rb
@@ -1,5 +1,15 @@
require "strscan"

# Some versions of the Bundler 1.1 RC series introduced corrupted
# lockfiles. There were two major problems:
#
# * multiple copies of the same GIT section appeared in the lockfile
# * when this happened, those sections got multiple copies of gems
# in those sections.
#
# As a result, Bundler 1.1 contains code that fixes the earlier
# corruption. We will remove this fix-up code in Bundler 1.2.

module Bundler
class LockfileParser
attr_reader :sources, :dependencies, :specs, :platforms
Expand Down Expand Up @@ -37,6 +47,12 @@ def parse_source(line)
@opts, @type = {}, line
when " specs:"
@current_source = TYPES[@type].from_lock(@opts)

# Strip out duplicate GIT sections
if @sources.include?(@current_source)
@current_source = @sources.find { |s| s == @current_source }
end

@sources << @current_source
when /^ ([a-z]+): (.*)$/i
value = $2
Expand Down Expand Up @@ -89,7 +105,10 @@ def parse_spec(line)
platform = $3 ? Gem::Platform.new($3) : Gem::Platform::RUBY
@current_spec = LazySpecification.new(name, version, platform)
@current_spec.source = @current_source
@specs << @current_spec

# Avoid introducing multiple copies of the same spec (caused by
# duplicate GIT sections)
@specs << @current_spec unless @specs.include?(@current_spec)
elsif line =~ %r{^ {6}#{NAME_VERSION}$}
name, version = $1, $2
version = version.split(',').map { |d| d.strip } if version
Expand Down
70 changes: 70 additions & 0 deletions spec/lock/lockfile_spec.rb
Expand Up @@ -670,6 +670,76 @@

end

# Some versions of the Bundler 1.1 RC series introduced corrupted
# lockfiles. There were two major problems:
#
# * multiple copies of the same GIT section appeared in the lockfile
# * when this happened, those sections got multiple copies of gems
# in those sections.
it "fix corrupted lockfiles" do
build_git "omg", :path => lib_path('omg')
revision = revision_for(lib_path('omg'))

gemfile <<-G
source "file://#{gem_repo1}"
gem "omg", :git => "#{lib_path('omg')}", :branch => 'master'
G

bundle "install --path vendor"
should_be_installed "omg 1.0"

# Create a Gemfile.lock that has duplicate GIT sections
lockfile <<-L
GIT
remote: #{lib_path('omg')}
revision: #{revision}
branch: master
specs:
omg (1.0)
GIT
remote: #{lib_path('omg')}
revision: #{revision}
branch: master
specs:
omg (1.0)
GEM
remote: file:#{gem_repo1}/
specs:
PLATFORMS
#{local}
DEPENDENCIES
omg!
L

FileUtils.rm_rf(bundled_app('vendor'))
bundle "install"
should_be_installed "omg 1.0"

# Confirm that duplicate specs do not appear
File.read(bundled_app('Gemfile.lock')).should == strip_whitespace(<<-L)
GIT
remote: #{lib_path('omg')}
revision: #{revision}
branch: master
specs:
omg (1.0)
GEM
remote: file:#{gem_repo1}/
specs:
PLATFORMS
#{local}
DEPENDENCIES
omg!
L
end

describe "line endings" do
def set_lockfile_mtime_to_known_value
time = Time.local(2000, 1, 1, 0, 0, 0)
Expand Down
6 changes: 6 additions & 0 deletions spec/support/helpers.rb
Expand Up @@ -162,6 +162,12 @@ def lockfile(*args)
end
end

def strip_whitespace(str)
# Trim the leading spaces
spaces = str[/\A\s+/, 0] || ""
str.gsub(/^#{spaces}/, '')
end

def install_gemfile(*args)
gemfile(*args)
opts = args.last.is_a?(Hash) ? args.last : {}
Expand Down

0 comments on commit e31a1c7

Please sign in to comment.