# frozen_string_literal: true

RSpec.describe "bundle install with git sources" do
  describe "when floating on main" do
    let(:base_gemfile) do
      <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}" do
          gem 'foo'
        end
      G
    end

    let(:install_base_gemfile) do
      build_git "foo" do |s|
        s.executables = "foobar"
      end

      install_gemfile base_gemfile
    end

    it "fetches gems" do
      install_base_gemfile
      expect(the_bundle).to include_gems("foo 1.0")

      run <<-RUBY
        require 'foo'
        puts "WIN" unless defined?(FOO_PREV_REF)
      RUBY

      expect(out).to eq("WIN")
    end

    it "does not (yet?) enforce CHECKSUMS" do
      build_git "foo"
      revision = revision_for(lib_path("foo-1.0"))

      bundle "config set lockfile_checksums true"
      gemfile base_gemfile

      lockfile <<~L
        GIT
          remote: #{lib_path("foo-1.0")}
          revision: #{revision}
          specs:
            foo (1.0)

        GEM
          remote: https://gem.repo1/
          specs:

        PLATFORMS
          #{lockfile_platforms}

        DEPENDENCIES
          foo!

        CHECKSUMS
          foo (1.0)

        BUNDLED WITH
          #{Bundler::VERSION}
      L

      bundle "config set frozen true"

      bundle "install"
      expect(the_bundle).to include_gems("foo 1.0")
    end

    it "caches the git repo" do
      install_base_gemfile
      expect(Dir["#{default_cache_path}/git/foo-1.0-*"]).to have_attributes size: 1
    end

    it "does not write to cache on bundler/setup" do
      install_base_gemfile
      FileUtils.rm_r(default_cache_path)
      ruby "require 'bundler/setup'"
      expect(default_cache_path).not_to exist
    end

    it "caches the git repo globally and properly uses the cached repo on the next invocation" do
      install_base_gemfile
      pristine_system_gems
      bundle "config set global_gem_cache true"
      bundle :install
      expect(Dir["#{home}/.bundle/cache/git/foo-1.0-*"]).to have_attributes size: 1

      bundle "install --verbose"
      expect(err).to be_empty
      expect(out).to include("Using foo 1.0 from #{lib_path("foo")}")
    end

    it "caches the evaluated gemspec" do
      install_base_gemfile
      git = update_git "foo" do |s|
        s.executables = ["foobar"] # we added this the first time, so keep it now
        s.files = ["bin/foobar"] # updating git nukes the files list
        foospec = s.to_ruby.gsub(/s\.files.*/, 's.files = `git ls-files -z`.split("\x0")')
        s.write "foo.gemspec", foospec
      end

      bundle "update foo"

      sha = git.ref_for("main", 11)
      spec_file = default_bundle_path("bundler/gems/foo-1.0-#{sha}/foo.gemspec")
      expect(spec_file).to exist
      ruby_code = Gem::Specification.load(spec_file.to_s).to_ruby
      file_code = File.read(spec_file)
      expect(file_code).to eq(ruby_code)
    end

    it "does not update the git source implicitly" do
      install_base_gemfile
      update_git "foo"

      install_gemfile bundled_app2("Gemfile"), <<-G, dir: bundled_app2
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}" do
          gem 'foo'
        end
      G

      run <<-RUBY
        require 'foo'
        puts "fail" if defined?(FOO_PREV_REF)
      RUBY

      expect(out).to be_empty
    end

    it "sets up git gem executables on the path" do
      install_base_gemfile
      bundle "exec foobar"
      expect(out).to eq("1.0")
    end

    it "complains if pinned specs don't exist in the git repo" do
      build_git "foo"

      install_gemfile <<-G, raise_on_error: false
        source "https://gem.repo1"
        gem "foo", "1.1", :git => "#{lib_path("foo-1.0")}"
      G

      expect(err).to include("The source contains the following gems matching 'foo':\n  * foo-1.0")
    end

    it "complains with version and platform if pinned specs don't exist in the git repo", :jruby_only do
      build_git "only_java" do |s|
        s.platform = "java"
      end

      install_gemfile <<-G, raise_on_error: false
        source "https://gem.repo1"
        platforms :jruby do
          gem "only_java", "1.2", :git => "#{lib_path("only_java-1.0-java")}"
        end
      G

      expect(err).to include("The source contains the following gems matching 'only_java':\n  * only_java-1.0-java")
    end

    it "complains with multiple versions and platforms if pinned specs don't exist in the git repo", :jruby_only do
      build_git "only_java", "1.0" do |s|
        s.platform = "java"
      end

      build_git "only_java", "1.1" do |s|
        s.platform = "java"
        s.write "only_java1-0.gemspec", File.read("#{lib_path("only_java-1.0-java")}/only_java.gemspec")
      end

      install_gemfile <<-G, raise_on_error: false
        source "https://gem.repo1"
        platforms :jruby do
          gem "only_java", "1.2", :git => "#{lib_path("only_java-1.1-java")}"
        end
      G

      expect(err).to include("The source contains the following gems matching 'only_java':\n  * only_java-1.0-java\n  * only_java-1.1-java")
    end

    it "still works after moving the application directory" do
      bundle "config set --local path vendor/bundle"
      install_base_gemfile

      FileUtils.mv bundled_app, tmp("bundled_app.bck")

      expect(the_bundle).to include_gems "foo 1.0", dir: tmp("bundled_app.bck")
    end

    it "can still install after moving the application directory" do
      bundle "config set --local path vendor/bundle"
      install_base_gemfile

      FileUtils.mv bundled_app, tmp("bundled_app.bck")

      update_git "foo", "1.1", path: lib_path("foo-1.0")

      gemfile tmp("bundled_app.bck/Gemfile"), <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}" do
          gem 'foo'
        end

        gem "myrack", "1.0"
      G

      bundle "update foo", dir: tmp("bundled_app.bck")

      expect(the_bundle).to include_gems "foo 1.1", "myrack 1.0", dir: tmp("bundled_app.bck")
    end
  end

  describe "with an empty git block" do
    before do
      build_git "foo"
      gemfile <<-G
        source "https://gem.repo1"
        gem "myrack"

        git "#{lib_path("foo-1.0")}" do
          # this page left intentionally blank
        end
      G
    end

    it "does not explode" do
      bundle "install"
      expect(the_bundle).to include_gems "myrack 1.0"
    end
  end

  describe "when specifying a revision" do
    before(:each) do
      build_git "foo"
      @revision = revision_for(lib_path("foo-1.0"))
      update_git "foo"
    end

    it "works" do
      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}", :ref => "#{@revision}" do
          gem "foo"
        end
      G
      expect(err).to be_empty

      run <<-RUBY
        require 'foo'
        puts "WIN" unless defined?(FOO_PREV_REF)
      RUBY

      expect(out).to eq("WIN")
    end

    it "works when the revision is a symbol" do
      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}", :ref => #{@revision.to_sym.inspect} do
          gem "foo"
        end
      G
      expect(err).to be_empty

      run <<-RUBY
        require 'foo'
        puts "WIN" unless defined?(FOO_PREV_REF)
      RUBY

      expect(out).to eq("WIN")
    end

    it "works when an abbreviated revision is added after an initial, potentially shallow clone" do
      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}" do
          gem "foo"
        end
      G

      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}", :ref => #{@revision[0..7].inspect} do
          gem "foo"
        end
      G
    end

    it "works when a tag that does not look like a commit hash is used as the value of :ref" do
      build_git "foo"
      @remote = build_git("bar", bare: true)
      update_git "foo", remote: @remote.path
      update_git "foo", push: "main"

      install_gemfile <<-G
        source "https://gem.repo1"
        gem 'foo', :git => "#{@remote.path}"
      G

      # Create a new tag on the remote that needs fetching
      update_git "foo", tag: "v1.0.0"
      update_git "foo", push: "v1.0.0"

      install_gemfile <<-G
        source "https://gem.repo1"
        gem 'foo', :git => "#{@remote.path}", :ref => "v1.0.0"
      G

      expect(err).to be_empty
    end

    it "works when the revision is a non-head ref" do
      # want to ensure we don't fallback to main
      update_git "foo", path: lib_path("foo-1.0") do |s|
        s.write("lib/foo.rb", "raise 'FAIL'")
      end

      git("update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", lib_path("foo-1.0"))

      # want to ensure we don't fallback to HEAD
      update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s|
        s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'")
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do
          gem "foo"
        end
      G
      expect(err).to be_empty

      run <<-RUBY
        require 'foo'
        puts "WIN" if defined?(FOO)
      RUBY

      expect(out).to eq("WIN")
    end

    it "works when the revision is a non-head ref and it was previously downloaded" do
      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}" do
          gem "foo"
        end
      G

      # want to ensure we don't fallback to main
      update_git "foo", path: lib_path("foo-1.0") do |s|
        s.write("lib/foo.rb", "raise 'FAIL'")
      end

      git("update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", lib_path("foo-1.0"))

      # want to ensure we don't fallback to HEAD
      update_git "foo", path: lib_path("foo-1.0"), branch: "rando" do |s|
        s.write("lib/foo.rb", "raise 'FAIL_FROM_RANDO'")
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}", :ref => "refs/bundler/1" do
          gem "foo"
        end
      G
      expect(err).to be_empty

      run <<-RUBY
        require 'foo'
        puts "WIN" if defined?(FOO)
      RUBY

      expect(out).to eq("WIN")
    end

    it "does not download random non-head refs" do
      git("update-ref -m \"Bundler Spec!\" refs/bundler/1 main~1", lib_path("foo-1.0"))

      bundle "config set global_gem_cache true"

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

      # ensure we also git fetch after cloning
      bundle :update, all: true

      git("ls-remote .", Dir[home(".bundle/cache/git/foo-*")].first)

      expect(out).not_to include("refs/bundler/1")
    end
  end

  describe "when specifying a branch" do
    let(:branch) { "branch" }
    let(:repo) { build_git("foo").path }

    it "works" do
      update_git("foo", path: repo, branch: branch)

      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{repo}", :branch => #{branch.dump} do
          gem "foo"
        end
      G

      expect(the_bundle).to include_gems("foo 1.0")
    end

    context "when the branch starts with a `#`" do
      let(:branch) { "#149/redirect-url-fragment" }
      it "works" do
        skip "git does not accept this" if Gem.win_platform?

        update_git("foo", path: repo, branch: branch)

        install_gemfile <<-G
          source "https://gem.repo1"
          git "#{repo}", :branch => #{branch.dump} do
            gem "foo"
          end
        G

        expect(the_bundle).to include_gems("foo 1.0")
      end
    end

    context "when the branch includes quotes" do
      let(:branch) { %('") }
      it "works" do
        skip "git does not accept this" if Gem.win_platform?

        update_git("foo", path: repo, branch: branch)

        install_gemfile <<-G
          source "https://gem.repo1"
          git "#{repo}", :branch => #{branch.dump} do
            gem "foo"
          end
        G

        expect(the_bundle).to include_gems("foo 1.0")
      end
    end
  end

  describe "when specifying a tag" do
    let(:tag) { "tag" }
    let(:repo) { build_git("foo").path }

    it "works" do
      update_git("foo", path: repo, tag: tag)

      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{repo}", :tag => #{tag.dump} do
          gem "foo"
        end
      G

      expect(the_bundle).to include_gems("foo 1.0")
    end

    context "when the tag starts with a `#`" do
      let(:tag) { "#149/redirect-url-fragment" }
      it "works" do
        skip "git does not accept this" if Gem.win_platform?

        update_git("foo", path: repo, tag: tag)

        install_gemfile <<-G
          source "https://gem.repo1"
          git "#{repo}", :tag => #{tag.dump} do
            gem "foo"
          end
        G

        expect(the_bundle).to include_gems("foo 1.0")
      end
    end

    context "when the tag includes quotes" do
      let(:tag) { %('") }
      it "works" do
        skip "git does not accept this" if Gem.win_platform?

        update_git("foo", path: repo, tag: tag)

        install_gemfile <<-G
          source "https://gem.repo1"
          git "#{repo}", :tag => #{tag.dump} do
            gem "foo"
          end
        G

        expect(the_bundle).to include_gems("foo 1.0")
      end
    end
  end

  describe "when specifying local override" do
    it "uses the local repository instead of checking a new one out" do
      build_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
        s.write "lib/myrack.rb", "puts :LOCAL"
      end

      gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
      G

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      bundle :install

      run "require 'myrack'"
      expect(out).to eq("LOCAL")
    end

    it "chooses the local repository on runtime" do
      build_git "myrack", "0.8"

      FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))

      update_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
        s.write "lib/myrack.rb", "puts :LOCAL"
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
      G

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      run "require 'myrack'"
      expect(out).to eq("LOCAL")
    end

    it "unlocks the source when the dependencies have changed while switching to the local" do
      build_git "myrack", "0.8"

      FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))

      update_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
        s.write "myrack.gemspec", build_spec("myrack", "0.8") { runtime "rspec", "> 0" }.first.to_ruby
        s.write "lib/myrack.rb", "puts :LOCAL"
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
      G

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      bundle :install
      run "require 'myrack'"
      expect(out).to eq("LOCAL")
    end

    it "updates specs on runtime" do
      system_gems "nokogiri-1.4.2"

      build_git "myrack", "0.8"

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
      G

      lockfile0 = File.read(bundled_app_lock)

      FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
      update_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
        s.add_dependency "nokogiri", "1.4.2"
      end

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      run "require 'myrack'"

      lockfile1 = File.read(bundled_app_lock)
      expect(lockfile1).not_to eq(lockfile0)
    end

    it "updates ref on install" do
      build_git "myrack", "0.8"

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
      G

      lockfile0 = File.read(bundled_app_lock)

      FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))
      update_git "myrack", "0.8", path: lib_path("local-myrack")

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      bundle :install

      lockfile1 = File.read(bundled_app_lock)
      expect(lockfile1).not_to eq(lockfile0)
    end

    it "explodes and gives correct solution if given path does not exist on install" do
      build_git "myrack", "0.8"

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
      G

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      bundle :install, raise_on_error: false
      expect(err).to match(/Cannot use local override for myrack-0.8 because #{Regexp.escape(lib_path("local-myrack").to_s)} does not exist/)

      solution = "config unset local.myrack"
      expect(err).to match(/Run `bundle #{solution}` to remove the local override/)

      bundle solution
      bundle :install

      expect(err).to be_empty
    end

    it "explodes and gives correct solution if branch is not given on install" do
      build_git "myrack", "0.8"
      FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}"
      G

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      bundle :install, raise_on_error: false
      expect(err).to match(/Cannot use local override for myrack-0.8 at #{Regexp.escape(lib_path("local-myrack").to_s)} because :branch is not specified in Gemfile/)

      solution = "config unset local.myrack"
      expect(err).to match(/Specify a branch or run `bundle #{solution}` to remove the local override/)

      bundle solution
      bundle :install

      expect(err).to be_empty
    end

    it "does not explode if disable_local_branch_check is given" do
      build_git "myrack", "0.8"
      FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}"
      G

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      bundle %(config set disable_local_branch_check true)
      bundle :install
      expect(out).to match(/Bundle complete!/)
    end

    it "explodes on different branches on install" do
      build_git "myrack", "0.8"

      FileUtils.cp_r("#{lib_path("myrack-0.8")}/.", lib_path("local-myrack"))

      update_git "myrack", "0.8", path: lib_path("local-myrack"), branch: "another" do |s|
        s.write "lib/myrack.rb", "puts :LOCAL"
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
      G

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      bundle :install, raise_on_error: false
      expect(err).to match(/is using branch another but Gemfile specifies main/)
    end

    it "explodes on invalid revision on install" do
      build_git "myrack", "0.8"

      build_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
        s.write "lib/myrack.rb", "puts :LOCAL"
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
      G

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      bundle :install, raise_on_error: false
      expect(err).to match(/The Gemfile lock is pointing to revision \w+/)
    end

    it "does not explode on invalid revision on install" do
      build_git "myrack", "0.8"

      build_git "myrack", "0.8", path: lib_path("local-myrack") do |s|
        s.write "lib/myrack.rb", "puts :LOCAL"
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}", :branch => "main"
      G

      bundle %(config set local.myrack #{lib_path("local-myrack")})
      bundle %(config set disable_local_revision_check true)
      bundle :install
      expect(out).to match(/Bundle complete!/)
    end
  end

  describe "specified inline" do
    # TODO: Figure out how to write this test so that it is not flaky depending
    #       on the current network situation.
    # it "supports private git URLs" do
    #   gemfile <<-G
    #     gem "thingy", :git => "git@notthere.fallingsnow.net:somebody/thingy.git"
    #   G
    #
    #   bundle :install
    #
    #   # p out
    #   # p err
    #   puts err unless err.empty? # This spec fails randomly every so often
    #   err.should include("notthere.fallingsnow.net")
    #   err.should include("ssh")
    # end

    it "installs from git even if a newer gem is available elsewhere" do
      build_git "myrack", "0.8"

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack-0.8")}"
      G

      expect(the_bundle).to include_gems "myrack 0.8"
    end

    it "installs dependencies from git even if a newer gem is available elsewhere" do
      system_gems "myrack-1.0.0"

      build_lib "myrack", "1.0", path: lib_path("nested/bar") do |s|
        s.write "lib/myrack.rb", "puts 'WIN OVERRIDE'"
      end

      build_git "foo", path: lib_path("nested") do |s|
        s.add_dependency "myrack", "= 1.0"
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("nested")}"
      G

      run "require 'myrack'"
      expect(out).to eq("WIN OVERRIDE")
    end

    it "correctly unlocks when changing to a git source" do
      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", "0.9.1"
      G

      build_git "myrack", path: lib_path("myrack")

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", "1.0.0", :git => "#{lib_path("myrack")}"
      G

      expect(the_bundle).to include_gems "myrack 1.0.0"
    end

    it "correctly unlocks when changing to a git source without versions" do
      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack"
      G

      build_git "myrack", "1.2", path: lib_path("myrack")

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", :git => "#{lib_path("myrack")}"
      G

      expect(the_bundle).to include_gems "myrack 1.2"
    end
  end

  describe "block syntax" do
    it "pulls all gems from a git block" do
      build_lib "omg", path: lib_path("hi2u/omg")
      build_lib "hi2u", path: lib_path("hi2u")

      install_gemfile <<-G
        source "https://gem.repo1"
        path "#{lib_path("hi2u")}" do
          gem "omg"
          gem "hi2u"
        end
      G

      expect(the_bundle).to include_gems "omg 1.0", "hi2u 1.0"
    end
  end

  it "uses a ref if specified" do
    build_git "foo"
    @revision = revision_for(lib_path("foo-1.0"))
    update_git "foo"

    install_gemfile <<-G
      source "https://gem.repo1"
      gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{@revision}"
    G

    run <<-RUBY
      require 'foo'
      puts "WIN" unless defined?(FOO_PREV_REF)
    RUBY

    expect(out).to eq("WIN")
  end

  it "correctly handles cases with invalid gemspecs" do
    build_git "foo" do |s|
      s.summary = nil
    end

    install_gemfile <<-G
      source "https://gem.repo1"
      gem "foo", :git => "#{lib_path("foo-1.0")}"
      gem "rails", "2.3.2"
    G

    expect(the_bundle).to include_gems "foo 1.0"
    expect(the_bundle).to include_gems "rails 2.3.2"
  end

  it "runs the gemspec in the context of its parent directory" do
    build_lib "bar", path: lib_path("foo/bar"), gemspec: false do |s|
      s.write lib_path("foo/bar/lib/version.rb"), %(BAR_VERSION = '1.0')
      s.write "bar.gemspec", <<-G
        $:.unshift Dir.pwd
        require 'lib/version'
        Gem::Specification.new do |s|
          s.name        = 'bar'
          s.author      = 'no one'
          s.version     = BAR_VERSION
          s.summary     = 'Bar'
          s.files       = Dir["lib/**/*.rb"]
        end
      G
    end

    build_git "foo", path: lib_path("foo") do |s|
      s.write "bin/foo", ""
    end

    install_gemfile <<-G
      source "https://gem.repo1"
      gem "bar", :git => "#{lib_path("foo")}"
      gem "rails", "2.3.2"
    G

    expect(the_bundle).to include_gems "bar 1.0"
    expect(the_bundle).to include_gems "rails 2.3.2"
  end

  it "runs the gemspec in the context of its parent directory, when using local overrides" do
    build_git "foo", path: lib_path("foo"), gemspec: false do |s|
      s.write lib_path("foo/lib/foo/version.rb"), %(FOO_VERSION = '1.0')
      s.write "foo.gemspec", <<-G
        $:.unshift Dir.pwd
        require 'lib/foo/version'
        Gem::Specification.new do |s|
          s.name        = 'foo'
          s.author      = 'no one'
          s.version     = FOO_VERSION
          s.summary     = 'Foo'
          s.files       = Dir["lib/**/*.rb"]
        end
      G
    end

    gemfile <<-G
      source "https://gem.repo1"
      gem "foo", :git => "https://github.com/gems/foo", branch: "main"
    G

    bundle %(config set local.foo #{lib_path("foo")})

    expect(the_bundle).to include_gems "foo 1.0"
  end

  it "installs from git even if a rubygems gem is present" do
    build_gem "foo", "1.0", path: lib_path("fake_foo"), to_system: true do |s|
      s.write "lib/foo.rb", "raise 'FAIL'"
    end

    build_git "foo", "1.0"

    install_gemfile <<-G
      source "https://gem.repo1"
      gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}"
    G

    expect(the_bundle).to include_gems "foo 1.0"
  end

  it "fakes the gem out if there is no gemspec" do
    build_git "foo", gemspec: false

    install_gemfile <<-G
      source "https://gem.repo1"
      gem "foo", "1.0", :git => "#{lib_path("foo-1.0")}"
      gem "rails", "2.3.2"
    G

    expect(the_bundle).to include_gems("foo 1.0")
    expect(the_bundle).to include_gems("rails 2.3.2")
  end

  it "catches git errors and spits out useful output" do
    gemfile <<-G
      source "https://gem.repo1"
      gem "foo", "1.0", :git => "omgomg"
    G

    bundle :install, raise_on_error: false

    expect(err).to include("Git error:")
    expect(err).to include("fatal")
    expect(err).to include("omgomg")
  end

  it "works when the gem path has spaces in it" do
    build_git "foo", path: lib_path("foo space-1.0")

    install_gemfile <<-G
      source "https://gem.repo1"
      gem "foo", :git => "#{lib_path("foo space-1.0")}"
    G

    expect(the_bundle).to include_gems "foo 1.0"
  end

  it "handles repos that have been force-pushed" do
    build_git "forced", "1.0"

    install_gemfile <<-G
      source "https://gem.repo1"
      git "#{lib_path("forced-1.0")}" do
        gem 'forced'
      end
    G
    expect(the_bundle).to include_gems "forced 1.0"

    update_git "forced" do |s|
      s.write "lib/forced.rb", "FORCED = '1.1'"
    end

    bundle "update", all: true
    expect(the_bundle).to include_gems "forced 1.1"

    git("reset --hard HEAD^", lib_path("forced-1.0"))

    bundle "update", all: true
    expect(the_bundle).to include_gems "forced 1.0"
  end

  it "ignores submodules if :submodule is not passed" do
    # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
    system(*%W[git config --global protocol.file.allow always])

    build_git "submodule", "1.0"
    build_git "has_submodule", "1.0" do |s|
      s.add_dependency "submodule"
    end
    git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0")
    git "commit -m \"submodulator\"", lib_path("has_submodule-1.0")

    install_gemfile <<-G, raise_on_error: false
      source "https://gem.repo1"
      git "#{lib_path("has_submodule-1.0")}" do
        gem "has_submodule"
      end
    G
    expect(err).to match(%r{submodule >= 0 could not be found in rubygems repository https://gem.repo1/ or installed locally})

    expect(the_bundle).not_to include_gems "has_submodule 1.0"
  end

  it "handles repos with submodules" do
    # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
    system(*%W[git config --global protocol.file.allow always])

    build_git "submodule", "1.0"
    build_git "has_submodule", "1.0" do |s|
      s.add_dependency "submodule"
    end
    git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0")
    git "commit -m \"submodulator\"", lib_path("has_submodule-1.0")

    install_gemfile <<-G
      source "https://gem.repo1"
      git "#{lib_path("has_submodule-1.0")}", :submodules => true do
        gem "has_submodule"
      end
    G

    expect(the_bundle).to include_gems "has_submodule 1.0"
  end

  it "does not warn when deiniting submodules" do
    # CVE-2022-39253: https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
    system(*%W[git config --global protocol.file.allow always])

    build_git "submodule", "1.0"
    build_git "has_submodule", "1.0"

    git "submodule add #{lib_path("submodule-1.0")} submodule-1.0", lib_path("has_submodule-1.0")
    git "commit -m \"submodulator\"", lib_path("has_submodule-1.0")

    install_gemfile <<-G
      source "https://gem.repo1"
      git "#{lib_path("has_submodule-1.0")}" do
        gem "has_submodule"
      end
    G
    expect(err).to be_empty

    expect(the_bundle).to include_gems "has_submodule 1.0"
    expect(the_bundle).to_not include_gems "submodule 1.0"
  end

  it "handles implicit updates when modifying the source info" do
    git = build_git "foo"

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

    update_git "foo"
    update_git "foo"

    install_gemfile <<-G
      source "https://gem.repo1"
      git "#{lib_path("foo-1.0")}", :ref => "#{git.ref_for("HEAD^")}" do
        gem "foo"
      end
    G

    run <<-RUBY
      require 'foo'
      puts "WIN" if FOO_PREV_REF == '#{git.ref_for("HEAD^^")}'
    RUBY

    expect(out).to eq("WIN")
  end

  it "does not do a remote fetch if the revision is cached locally" do
    build_git "foo"

    install_gemfile <<-G
      source "https://gem.repo1"
      gem "foo", :git => "#{lib_path("foo-1.0")}"
    G

    FileUtils.rm_r(lib_path("foo-1.0"))

    bundle "install"
    expect(out).not_to match(/updating/i)
  end

  it "doesn't blow up if bundle install is run twice in a row" do
    build_git "foo"

    gemfile <<-G
      source "https://gem.repo1"
      gem "foo", :git => "#{lib_path("foo-1.0")}"
    G

    bundle "install"
    bundle "install"
  end

  it "prints a friendly error if a file blocks the git repo" do
    build_git "foo"

    FileUtils.mkdir_p(default_bundle_path)
    FileUtils.touch(default_bundle_path("bundler"))

    install_gemfile <<-G, raise_on_error: false
      source "https://gem.repo1"
      gem "foo", :git => "#{lib_path("foo-1.0")}"
    G

    expect(last_command).to be_failure
    expect(err).to include("Bundler could not install a gem because it " \
                           "needs to create a directory, but a file exists " \
                           "- #{default_bundle_path("bundler")}")
  end

  it "does not duplicate git gem sources" do
    build_lib "foo", path: lib_path("nested/foo")
    build_lib "bar", path: lib_path("nested/bar")

    build_git "foo", path: lib_path("nested")
    build_git "bar", path: lib_path("nested")

    install_gemfile <<-G
      source "https://gem.repo1"
      gem "foo", :git => "#{lib_path("nested")}"
      gem "bar", :git => "#{lib_path("nested")}"
    G

    expect(File.read(bundled_app_lock).scan("GIT").size).to eq(1)
  end

  describe "switching sources" do
    it "doesn't explode when switching Path to Git sources" do
      build_gem "foo", "1.0", to_system: true do |s|
        s.write "lib/foo.rb", "raise 'fail'"
      end
      build_lib "foo", "1.0", path: lib_path("bar/foo")
      build_git "bar", "1.0", path: lib_path("bar") do |s|
        s.add_dependency "foo"
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "bar", :path => "#{lib_path("bar")}"
      G

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "bar", :git => "#{lib_path("bar")}"
      G

      expect(the_bundle).to include_gems "foo 1.0", "bar 1.0"
    end

    it "doesn't explode when switching Gem to Git source" do
      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack-obama"
        gem "myrack", "1.0.0"
      G

      build_git "myrack", "1.0" do |s|
        s.write "lib/new_file.rb", "puts 'USING GIT'"
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack-obama"
        gem "myrack", "1.0.0", :git => "#{lib_path("myrack-1.0")}"
      G

      run "require 'new_file'"
      expect(out).to eq("USING GIT")
    end

    it "doesn't explode when removing an explicit exact version from a git gem with dependencies" do
      build_lib "activesupport", "7.1.4", path: lib_path("rails/activesupport")
      build_git "rails", "7.1.4", path: lib_path("rails") do |s|
        s.add_dependency "activesupport", "= 7.1.4"
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "rails", "7.1.4", :git => "#{lib_path("rails")}"
      G

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "rails", :git => "#{lib_path("rails")}"
      G

      expect(the_bundle).to include_gem "rails 7.1.4", "activesupport 7.1.4"
    end

    it "doesn't explode when adding an explicit ref to a git gem with dependencies" do
      lib_root = lib_path("rails")

      build_lib "activesupport", "7.1.4", path: lib_root.join("activesupport")
      build_git "rails", "7.1.4", path: lib_root do |s|
        s.add_dependency "activesupport", "= 7.1.4"
      end

      old_revision = revision_for(lib_root)
      update_git "rails", "7.1.4", path: lib_root

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "rails", "7.1.4", :git => "#{lib_root}"
      G

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "rails", :git => "#{lib_root}", :ref => "#{old_revision}"
      G

      expect(the_bundle).to include_gem "rails 7.1.4", "activesupport 7.1.4"
    end
  end

  describe "bundle install after the remote has been updated" do
    it "installs" do
      build_git "valim"

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "valim", :git => "#{lib_path("valim-1.0")}"
      G

      old_revision = revision_for(lib_path("valim-1.0"))
      update_git "valim"
      new_revision = revision_for(lib_path("valim-1.0"))

      old_lockfile = File.read(bundled_app_lock)
      lockfile(bundled_app_lock, old_lockfile.gsub(/revision: #{old_revision}/, "revision: #{new_revision}"))

      bundle "install"

      run <<-R
        require "valim"
        puts VALIM_PREV_REF
      R

      expect(out).to eq(old_revision)
    end

    it "gives a helpful error message when the remote ref no longer exists" do
      build_git "foo"
      revision = revision_for(lib_path("foo-1.0"))

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{revision}"
      G
      expect(out).to_not match(/Revision.*does not exist/)

      install_gemfile <<-G, raise_on_error: false
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "deadbeef"
      G
      expect(err).to include("Revision deadbeef does not exist in the repository")
    end

    it "gives a helpful error message when the remote branch no longer exists" do
      build_git "foo"

      install_gemfile <<-G, env: { "LANG" => "en" }, raise_on_error: false
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "deadbeef"
      G

      expect(err).to include("Revision deadbeef does not exist in the repository")
    end
  end

  describe "bundle install with deployment mode configured and git sources" do
    it "works" do
      build_git "valim", path: lib_path("valim")

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "valim", "= 1.0", :git => "#{lib_path("valim")}"
      G

      pristine_system_gems

      bundle "config set --local deployment true"
      bundle :install
    end
  end

  describe "gem install hooks" do
    it "runs pre-install hooks" do
      build_git "foo"
      gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      File.open(lib_path("install_hooks.rb"), "w") do |h|
        h.write <<-H
          Gem.pre_install_hooks << lambda do |inst|
            STDERR.puts "Ran pre-install hook: \#{inst.spec.full_name}"
          end
        H
      end

      bundle :install,
        requires: [lib_path("install_hooks.rb")]
      expect(err_without_deprecations).to eq("Ran pre-install hook: foo-1.0")
    end

    it "runs post-install hooks" do
      build_git "foo"
      gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      File.open(lib_path("install_hooks.rb"), "w") do |h|
        h.write <<-H
          Gem.post_install_hooks << lambda do |inst|
            STDERR.puts "Ran post-install hook: \#{inst.spec.full_name}"
          end
        H
      end

      bundle :install,
        requires: [lib_path("install_hooks.rb")]
      expect(err_without_deprecations).to eq("Ran post-install hook: foo-1.0")
    end

    it "complains if the install hook fails" do
      build_git "foo"
      gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      File.open(lib_path("install_hooks.rb"), "w") do |h|
        h.write <<-H
          Gem.pre_install_hooks << lambda do |inst|
            false
          end
        H
      end

      bundle :install, requires: [lib_path("install_hooks.rb")], raise_on_error: false
      expect(err).to include("failed for foo-1.0")
    end
  end

  context "with an extension" do
    it "installs the extension" do
      build_git "foo" do |s|
        s.add_dependency "rake"
        s.extensions << "Rakefile"
        s.write "Rakefile", <<-RUBY
          task :default do
            path = File.expand_path("lib", __dir__)
            FileUtils.mkdir_p(path)
            File.open("\#{path}/foo.rb", "w") do |f|
              f.puts "FOO = 'YES'"
            end
          end
        RUBY
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      run <<-R
        require 'foo'
        puts FOO
      R
      expect(out).to eq("YES")

      run <<-R
        puts $:.grep(/ext/)
      R
      expect(out).to include(Pathname.glob(default_bundle_path("bundler/gems/extensions/**/foo-1.0-*")).first.to_s)
    end

    it "does not use old extension after ref changes" do
      git_reader = build_git "foo", no_default: true do |s|
        s.extensions = ["ext/extconf.rb"]
        s.write "ext/extconf.rb", <<-RUBY
          require "mkmf"
          create_makefile("foo")
        RUBY
        s.write "ext/foo.c", "void Init_foo() {}"
      end

      2.times do |i|
        File.open(git_reader.path.join("ext/foo.c"), "w") do |file|
          file.write <<-C
            #include "ruby.h"
            VALUE foo(VALUE self) { return INT2FIX(#{i}); }
            void Init_foo() { rb_define_global_function("foo", &foo, 0); }
          C
        end
        git("commit -m \"commit for iteration #{i}\" ext/foo.c", git_reader.path)

        git_commit_sha = git_reader.ref_for("HEAD")

        install_gemfile <<-G
          source "https://gem.repo1"
          gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{git_commit_sha}"
        G

        run <<-R
          require 'foo'
          puts foo
        R

        expect(out).to eq(i.to_s)
      end
    end

    it "does not prompt to gem install if extension fails" do
      build_git "foo" do |s|
        s.add_dependency "rake"
        s.extensions << "Rakefile"
        s.write "Rakefile", <<-RUBY
          task :default do
            raise
          end
        RUBY
      end

      install_gemfile <<-G, raise_on_error: false
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      expect(err).to end_with(<<-M.strip)
An error occurred while installing foo (1.0), and Bundler cannot continue.

In Gemfile:
  foo
      M
      expect(out).not_to include("gem install foo")
    end

    it "does not reinstall the extension" do
      build_git "foo" do |s|
        s.add_dependency "rake"
        s.extensions << "Rakefile"
        s.write "Rakefile", <<-RUBY
          task :default do
            path = File.expand_path("lib", __dir__)
            FileUtils.mkdir_p(path)
            cur_time = Time.now.to_f.to_s
            File.open("\#{path}/foo.rb", "w") do |f|
              f.puts "FOO = \#{cur_time}"
            end
          end
        RUBY
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      run <<-R
        require 'foo'
        puts FOO
      R

      installed_time = out
      expect(installed_time).to match(/\A\d+\.\d+\z/)

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      run <<-R
        require 'foo'
        puts FOO
      R
      expect(out).to eq(installed_time)
    end

    it "does not reinstall the extension when changing another gem" do
      build_git "foo" do |s|
        s.add_dependency "rake"
        s.extensions << "Rakefile"
        s.write "Rakefile", <<-RUBY
          task :default do
            path = File.expand_path("lib", __dir__)
            FileUtils.mkdir_p(path)
            cur_time = Time.now.to_f.to_s
            File.open("\#{path}/foo.rb", "w") do |f|
              f.puts "FOO = \#{cur_time}"
            end
          end
        RUBY
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", "0.9.1"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      run <<-R
        require 'foo'
        puts FOO
      R

      installed_time = out
      expect(installed_time).to match(/\A\d+\.\d+\z/)

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "myrack", "1.0.0"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      run <<-R
        require 'foo'
        puts FOO
      R
      expect(out).to eq(installed_time)
    end

    it "does reinstall the extension when changing refs" do
      build_git "foo" do |s|
        s.add_dependency "rake"
        s.extensions << "Rakefile"
        s.write "Rakefile", <<-RUBY
          task :default do
            path = File.expand_path("lib", __dir__)
            FileUtils.mkdir_p(path)
            cur_time = Time.now.to_f.to_s
            File.open("\#{path}/foo.rb", "w") do |f|
              f.puts "FOO = \#{cur_time}"
            end
          end
        RUBY
      end

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}"
      G

      run <<-R
        require 'foo'
        puts FOO
      R

      installed_time = out

      update_git("foo", branch: "branch2")

      expect(installed_time).to match(/\A\d+\.\d+\z/)

      install_gemfile <<-G
        source "https://gem.repo1"
        gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "branch2"
      G

      run <<-R
        require 'foo'
        puts FOO
      R
      expect(out).not_to eq(installed_time)

      installed_time = out

      update_git("foo")
      bundle "update foo"

      run <<-R
        require 'foo'
        puts FOO
      R
      expect(out).not_to eq(installed_time)
    end
  end

  it "ignores git environment variables" do
    build_git "xxxxxx" do |s|
      s.executables = "xxxxxxbar"
    end

    Bundler::SharedHelpers.with_clean_git_env do
      ENV["GIT_DIR"]       = "bar"
      ENV["GIT_WORK_TREE"] = "bar"

      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{lib_path("xxxxxx-1.0")}" do
          gem 'xxxxxx'
        end
      G

      expect(ENV["GIT_DIR"]).to eq("bar")
      expect(ENV["GIT_WORK_TREE"]).to eq("bar")
    end
  end

  describe "without git installed" do
    it "prints a better error message when installing" do
      gemfile <<-G
        source "https://gem.repo1"

        gem "rake", git: "https://github.com/ruby/rake"
      G

      lockfile <<-L
        GIT
          remote: https://github.com/ruby/rake
          revision: 5c60da8644a9e4f655e819252e3b6ca77f42b7af
          specs:
            rake (13.0.6)

        GEM
          remote: https://rubygems.org/
          specs:

        PLATFORMS
          #{lockfile_platforms}

        DEPENDENCIES
          rake!

        BUNDLED WITH
          #{Bundler::VERSION}
      L

      with_path_as("") do
        bundle "install", raise_on_error: false
      end
      expect(err).
        to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git")
    end

    it "prints a better error message when updating" do
      build_git "foo"

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

      with_path_as("") do
        bundle "update", all: true, raise_on_error: false
      end
      expect(err).
        to include("You need to install git to be able to use gems from git repositories. For help installing git, please refer to GitHub's tutorial at https://help.github.com/articles/set-up-git")
    end

    it "doesn't need git in the new machine if an installed git gem is copied to another machine" do
      build_git "foo"

      install_gemfile <<-G
        source "https://gem.repo1"
        git "#{lib_path("foo-1.0")}" do
          gem 'foo'
        end
      G
      bundle "config set --global path vendor/bundle"
      bundle :install
      pristine_system_gems

      bundle "install", env: { "PATH" => "" }
      expect(out).to_not include("You need to install git to be able to use gems from git repositories.")
    end
  end

  describe "when the git source is overridden with a local git repo" do
    before do
      bundle "config set --global local.foo #{lib_path("foo")}"
    end

    describe "and git output is colorized" do
      before do
        File.open("#{ENV["HOME"]}/.gitconfig", "w") do |f|
          f.write("[color]\n\tui = always\n")
        end
      end

      it "installs successfully" do
        build_git "foo", "1.0", path: lib_path("foo")

        gemfile <<-G
          source "https://gem.repo1"
          gem "foo", :git => "#{lib_path("foo")}", :branch => "main"
        G

        bundle :install
        expect(the_bundle).to include_gems "foo 1.0"
      end
    end
  end

  context "git sources that include credentials" do
    context "that are username and password" do
      let(:credentials) { "user1:password1" }

      it "does not display the password" do
        install_gemfile <<-G, raise_on_error: false
          source "https://gem.repo1"
          git "https://#{credentials}@github.com/company/private-repo" do
            gem "foo"
          end
        G

        expect(stdboth).to_not include("password1")
        expect(out).to include("Fetching https://user1@github.com/company/private-repo")
      end
    end

    context "that is an oauth token" do
      let(:credentials) { "oauth_token" }

      it "displays the oauth scheme but not the oauth token" do
        install_gemfile <<-G, raise_on_error: false
          source "https://gem.repo1"
          git "https://#{credentials}:x-oauth-basic@github.com/company/private-repo" do
            gem "foo"
          end
        G

        expect(stdboth).to_not include("oauth_token")
        expect(out).to include("Fetching https://x-oauth-basic@github.com/company/private-repo")
      end
    end
  end
end
