Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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