# frozen_string_literal: true

require_relative "helper"
require "rubygems/source"

class TestGemSourceGit < Gem::TestCase
  def setup
    super

    @name, @version, @repository, @head = git_gem

    @hash = OpenSSL::Digest::SHA1.hexdigest @repository

    @source = Gem::Source::Git.new @name, @repository, nil, false
  end

  def test_base_dir
    assert_equal File.join(Gem.dir, "bundler"), @source.base_dir

    @source.root_dir = "#{@gemhome}2"

    assert_equal File.join("#{@gemhome}2", "bundler"), @source.base_dir
  end

  def test_checkout
    @source.checkout

    assert_path_exist File.join @source.install_dir, "a.gemspec"
  end

  def test_checkout_default
    Dir.chdir @repository do
      default_branch = Gem::Util.popen(@git, "branch", "--show-current").strip
      system @git, "checkout", "-q", "-b", "other"
      system @git, "mv",             "a.gemspec", "b.gemspec"
      system @git, "commit",   "-q", "-a", "-m", "rename gemspec"
      system @git, "checkout", "-q", default_branch
    end

    @source = Gem::Source::Git.new @name, @repository, "other", false

    @source.checkout

    assert_path_exist File.join @source.install_dir, "b.gemspec"
  end

  def test_checkout_local
    @source.remote = false

    @source.checkout

    install_dir = File.join Gem.dir, "bundler", "gems", "a-#{@head[0..11]}"

    assert_path_not_exist File.join install_dir, "a.gemspec"
  end

  def test_checkout_local_cached
    @source.cache

    @source.remote = false

    @source.checkout

    assert_path_exist File.join @source.install_dir, "a.gemspec"
  end

  def test_checkout_submodules
    # We need to allow to checkout submodules with file:// protocol
    # CVE-2022-39253
    # https://lore.kernel.org/lkml/xmqq4jw1uku5.fsf@gitster.g/
    system(@git, *%W[config --global protocol.file.allow always])

    source = Gem::Source::Git.new @name, @repository, nil, true

    git_gem "b"

    Dir.chdir "git/a" do
      require "open3"
      output, status = Open3.capture2e(@git, "submodule", "--quiet", "add", File.expand_path("../b"), "b")
      assert status.success?, output

      system @git, "commit", "--quiet", "-m", "add submodule b"
    end

    source.checkout

    assert_path_exist File.join source.install_dir, "a.gemspec"
    assert_path_exist File.join source.install_dir, "b/b.gemspec"
  end

  def test_cache
    assert @source.cache

    assert_path_exist @source.repo_cache_dir

    Dir.chdir @source.repo_cache_dir do
      assert_equal @head, Gem::Util.popen(@git, "rev-parse", "HEAD").strip
    end
  end

  def test_cache_local
    @source.remote = false

    @source.cache

    assert_path_not_exist @source.repo_cache_dir
  end

  def test_dir_shortref
    @source.cache

    assert_equal @head[0..11], @source.dir_shortref
  end

  def test_download
    refute @source.download nil, nil
  end

  def test_equals2
    assert_equal @source, @source

    assert_equal @source, @source.dup

    source =
      Gem::Source::Git.new @source.name, @source.repository, "other", false

    refute_equal @source, source

    source =
      Gem::Source::Git.new @source.name, "repo/other", @source.reference, false

    refute_equal @source, source

    source =
      Gem::Source::Git.new "b", @source.repository, @source.reference, false

    refute_equal @source, source

    source =
      Gem::Source::Git.new @source.name, @source.repository, @source.reference,
                           true

    refute_equal @source, source
  end

  def test_install_dir
    @source.cache

    expected = File.join Gem.dir, "bundler", "gems", "a-#{@head[0..11]}"

    assert_equal expected, @source.install_dir
  end

  def test_install_dir_local
    @source.remote = false

    assert_nil @source.install_dir
  end

  def test_repo_cache_dir
    expected =
      File.join Gem.dir, "cache", "bundler", "git", "a-#{@hash}"

    assert_equal expected, @source.repo_cache_dir

    @source.root_dir = "#{@gemhome}2"

    expected =
      File.join "#{@gemhome}2", "cache", "bundler", "git", "a-#{@hash}"

    assert_equal expected, @source.repo_cache_dir
  end

  def test_rev_parse
    @source.cache

    assert_equal @head, @source.rev_parse

    Dir.chdir @repository do
      system @git, "checkout", "--quiet", "-b", "other"
    end

    default_head = @head

    git_gem "a", 2

    source = Gem::Source::Git.new @name, @repository, "other", false

    source.cache

    refute_equal default_head, source.rev_parse

    source = Gem::Source::Git.new @name, @repository, "nonexistent", false

    source.cache

    e = assert_raise Gem::Exception do
      capture_subprocess_io { source.rev_parse }
    end

    assert_equal "unable to find reference nonexistent in #{@repository}",
                   e.message
  end

  def test_root_dir
    assert_equal Gem.dir, @source.root_dir

    @source.root_dir = "#{@gemhome}2"

    assert_equal "#{@gemhome}2", @source.root_dir
  end

  def test_spaceship
    git       = Gem::Source::Git.new "a", "git/a", nil, false
    remote    = Gem::Source.new @gem_repo
    installed = Gem::Source::Installed.new
    vendor    = Gem::Source::Vendor.new "vendor/foo"

    assert_equal(0, git.<=>(git),       "git <=> git") # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands

    assert_equal(1, git.<=>(remote),    "git <=> remote")
    assert_equal(-1, remote.<=>(git), "remote <=> git")

    assert_equal(1, git.<=>(installed), "git <=> installed")
    assert_equal(-1, installed.<=>(git), "installed <=> git")

    assert_equal(-1, git.<=>(vendor), "git <=> vendor")
    assert_equal(1, vendor.<=>(git), "vendor <=> git")
  end

  def test_specs
    source = Gem::Source::Git.new @name, @repository, nil, true

    Dir.chdir "git/a" do
      FileUtils.mkdir "b"

      Dir.chdir "b" do
        b = Gem::Specification.new "b", 1

        File.open "b.gemspec", "w" do |io|
          io.write b.to_ruby
        end

        system @git, "add", "b.gemspec"
        system @git, "commit", "--quiet", "-m", "add b/b.gemspec"
      end
    end

    specs = nil

    capture_output do
      specs = source.specs
    end

    assert_equal %w[a-1 b-1], specs.map(&:full_name)

    a_spec = specs.shift

    base_dir = File.dirname File.dirname source.install_dir

    assert_equal source.install_dir, a_spec.full_gem_path
    assert_equal File.join(source.install_dir, "a.gemspec"), a_spec.loaded_from
    assert_equal base_dir, a_spec.base_dir

    extension_dir =
      File.join Gem.dir, "bundler", "extensions",
        Gem::Platform.local.to_s, Gem.extension_api_version,
        "a-#{source.dir_shortref}"

    assert_equal extension_dir, a_spec.extension_dir

    b_spec = specs.shift

    assert_equal File.join(source.install_dir, "b"), b_spec.full_gem_path
    assert_equal File.join(source.install_dir, "b", "b.gemspec"),
                 b_spec.loaded_from
    assert_equal base_dir, b_spec.base_dir

    assert_equal extension_dir, b_spec.extension_dir
  end

  def test_specs_local
    source = Gem::Source::Git.new @name, @repository, nil, true
    source.remote = false

    capture_output do
      assert_empty source.specs
    end
  end

  def test_uri
    assert_equal Gem::URI(@repository), @source.uri
  end

  def test_pretty_print
    assert_equal "#<Gem::Source::Git[Git: #{@repository} HEAD]>", @source.pretty_inspect.gsub(/\s+/, " ").strip
  end

  def test_uri_hash
    assert_equal @hash, @source.uri_hash

    source =
      Gem::Source::Git.new "a", "http://git@example/repo.git", nil, false

    assert_equal "291c4caac7feba8bb64c297987028acb3dde6cfe",
                 source.uri_hash

    source =
      Gem::Source::Git.new "a", "HTTP://git@EXAMPLE/repo.git", nil, false

    assert_equal "291c4caac7feba8bb64c297987028acb3dde6cfe",
                 source.uri_hash
  end
end
