Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lib/bundler/source/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def initialize(options)

@copied = false
@local = false
@cached_app_cache_path = nil
end

def remote!
Expand Down Expand Up @@ -277,6 +278,11 @@ def cache_to(custom_path, try_migrate: false)

app_cache_path = app_cache_path(custom_path)

# When several gems share a single git source, this is called once per
# gem. Copying the repository is expensive for large repos, so skip it
# if we already populated this cache during the same command.
return if @cached_app_cache_path == app_cache_path

migrate = try_migrate ? bare_repo?(app_cache_path) : false

set_cache_path!(nil) if migrate
Expand All @@ -288,6 +294,8 @@ def cache_to(custom_path, try_migrate: false)
git_proxy.checkout if migrate || requires_checkout?
git_proxy.copy_to(app_cache_path, @submodules)
serialize_gemspecs_in(app_cache_path)

@cached_app_cache_path = app_cache_path
end

def checkout
Expand Down
28 changes: 28 additions & 0 deletions spec/bundler/source/git_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,32 @@
end
end
end

describe "#cache" do
let(:options) do
{ "uri" => uri, "revision" => "123abc" }
end
let(:app_cache_path) { Pathname.new("vendor/cache/bar-123abc") }
let(:git_proxy_stub) do
instance_double(Bundler::Source::Git::GitProxy, revision: "123abc", copy_to: nil)
end

before do
allow(Bundler::Source::Git::GitProxy).to receive(:new).and_return(git_proxy_stub)
allow(Bundler.settings).to receive(:[]).and_call_original
allow(Bundler.settings).to receive(:[]).with(:cache_all).and_return(true)
allow(subject).to receive(:app_cache_path).and_return(app_cache_path)
allow(subject).to receive(:cache_path).and_return(Pathname.new("global/git/bar-123abc"))
allow(subject).to receive(:requires_checkout?).and_return(false)
allow(subject).to receive(:serialize_gemspecs_in)
allow(FileUtils).to receive(:rm_rf)
end

it "copies the repository only once when several gems share the same source" do
subject.cache(double("spec for gem a"))
subject.cache(double("spec for gem b"))

expect(git_proxy_stub).to have_received(:copy_to).once
end
end
end
23 changes: 23 additions & 0 deletions spec/cache/git_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,29 @@
expect(the_bundle).to include_gems "foo 1.0"
end

it "caches a repository shared by multiple gems only once" do
build_lib "foo", path: lib_path("shared/foo")
build_lib "bar", path: lib_path("shared/bar")
build_git "foo", path: lib_path("shared")
git = build_git "bar", path: lib_path("shared")
ref = git.ref_for("main", 11)

install_gemfile <<-G
source "https://gem.repo1"
git "#{lib_path("shared")}" do
gem "foo"
gem "bar"
end
G

bundle :cache

expect(bundled_app("vendor/cache/shared-#{ref}")).to exist

FileUtils.rm_r lib_path("shared")
expect(the_bundle).to include_gems "foo 1.0", "bar 1.0"
end

it "tracks updates" do
git = build_git "foo"
old_ref = git.ref_for("main", 11)
Expand Down