require "spec_helper"
require "omnibus/manifest_entry"

module Omnibus
  describe GitFetcher do
    include_examples "a software"

    let(:remote)  { remote_git_repo("zlib") }
    let(:version) { "master" }

    let(:source) do
      { git: remote }
    end

    let(:manifest_entry) do
      double(ManifestEntry,
        name: "software",
        locked_version: "45ded6d3b1a35d66ed866b2c3eb418426e6382b0",
        described_version: version,
        locked_source: source)
    end

    subject { described_class.new(manifest_entry, project_dir, build_dir) }

    let(:revision) { shellout!("git rev-parse HEAD", cwd: project_dir).stdout.strip }

    describe "#fetch_required?" do
      context "when the repo is not cloned" do
        it "return true" do
          expect(subject.fetch_required?).to be_truthy
        end
      end

      context "when the repo is cloned" do
        before { subject.fetch }

        context "when the revision is not available" do
          let(:manifest_entry) do
            double(ManifestEntry,
              name: "software",
              locked_version: "abcdefabcdef5d66ed866b2c3eb418426e6382b0",
              described_version: version,
              locked_source: source)
          end

          it "return true" do
            expect(subject.fetch_required?).to be_truthy
          end
        end

        context "when the revisions are the same" do
          it "return false" do
            expect(subject.fetch_required?).to be(false)
          end
        end
      end
    end

    describe "#version_guid" do
      it "includes the current revision" do
        expect(subject.version_guid).to match(/^git:[0-9a-f]{40}/)
      end
    end

    describe "#clean" do
      before do
        subject.fetch
      end

      it "returns true" do
        expect(subject.clean).to be_truthy
      end

      context "when the project directory has extra files in it" do
        it "cleans the git repo" do
          create_file("#{project_dir}/file_a")
          create_file("#{project_dir}/.file_b")
          subject.clean
          expect("#{project_dir}/file_a").to_not be_a_file
          expect("#{project_dir}/.file_b").to_not be_a_file
        end
      end

      context "when the project directory is at a different version" do
        before do
          # Dirty the project_dir by giving it a conflicting commit.
          create_file("#{project_dir}/file_a") { "some new file" }
          create_file("#{project_dir}/configure") { "LALALALA" }
          shellout!("git add .", cwd: project_dir)
          shellout!('git commit -am "Some commit"', cwd: project_dir)
          create_file("#{project_dir}/.file_b")
        end

        it "checks out the right version" do
          subject.clean
          expect(revision).to eq(manifest_entry.locked_version)
        end

        it "resets the working tree" do
          subject.clean
          expect("#{project_dir}/file_a").to_not be_a_file
          expect("#{project_dir}/.file_b").to_not be_a_file
          expect(File.read("#{project_dir}/configure")).to_not match("LA")
        end
      end
    end

    describe "#fetch"  do
      let(:version)  { "v1.2.4" }
      let(:remote)   { remote_git_repo("zlib", annotated_tags: [version]) }
      let(:manifest_entry) do
        double(ManifestEntry,
          name: "software",
          locked_version: "efde208366abd0f91419d8a54b45e3f6e0540105",
          described_version: version,
          locked_source: source)
      end

      subject { described_class.new(manifest_entry, project_dir, build_dir) }

      it "clones the repository" do
        subject.fetch
        expect("#{project_dir}/.git").to be_a_directory
      end
    end

    describe "#resolve_version" do
      context "when the version is a tag" do
        let(:version)  { "v1.2.3" }
        let(:remote)   { remote_git_repo("zlib", tags: [version]) }

        it "parses the tag" do
          expect(GitFetcher.resolve_version(version, source)).to eq("53c72c4abcc961b153996f5b5f402ce715e47146")
        end
      end

      context "when the version is an annnotated tag" do
        let(:version)  { "v1.2.4" }
        let(:remote)   { remote_git_repo("zlib", annotated_tags: [version]) }

        it "it defererences and parses the annotated tag" do
          expect(GitFetcher.resolve_version(version, source)).to eq("efde208366abd0f91419d8a54b45e3f6e0540105")
        end
      end

      context "when the version is a branch" do
        let(:version) { "sethvargo/magic_ponies" }

        context "when only one branch matches the specified name" do
          let(:remote) { remote_git_repo("zlib", branches: [version]) }

          it "parses the branch" do
            expect(GitFetcher.resolve_version(version, source)).to eq("171a1aec35ac0a050f8dccd9c9ef4609b1d8d8ea")
          end
        end

        context "when multiple branches match the specified name" do
          let(:remote) { remote_git_repo("zlib", branches: [version, "another/#{version}"]) }

          it "parses the branch" do
            expect(GitFetcher.resolve_version(version, source)).to eq("171a1aec35ac0a050f8dccd9c9ef4609b1d8d8ea")
          end
        end
      end

      context "when the version is a full SHA-1" do
        let(:version) { "45ded6d3b1a35d66ed866b2c3eb418426e6382b0" }
        let(:remote)  { remote_git_repo("zlib") }

        it "parses the full SHA-1" do
          expect(GitFetcher.resolve_version(version, source)).to eq("45ded6d3b1a35d66ed866b2c3eb418426e6382b0")
        end
      end

      context "when the version is a abbreviated SHA-1" do
        let(:version) { "45ded6d" }
        let(:remote)  { remote_git_repo("zlib") }

        it "parses the abbreviated SHA-1" do
          expect(GitFetcher.resolve_version(version, source)).to eq("45ded6d")
        end
      end

      context "when the version is a non-existent ref" do
        let(:version) { "fufufufufu" }
        let(:remote)  { remote_git_repo("zlib") }

        it "raise an exception" do
          expect { GitFetcher.resolve_version(version, source) }.to raise_error(UnresolvableGitReference)
        end
      end
    end

    describe "#version_for_cache" do
      it "includes the resolved revision" do
        expect(subject.version_for_cache).to eq("revision:45ded6d3b1a35d66ed866b2c3eb418426e6382b0")
      end

      it "not use the current version on disk after fetching" do
        expect(subject.version_for_cache).to eq("revision:45ded6d3b1a35d66ed866b2c3eb418426e6382b0")
        subject.fetch
        expect(subject.version_for_cache).to eq("revision:45ded6d3b1a35d66ed866b2c3eb418426e6382b0")
        expect(revision).to_not eq("revision:45ded6d3b1a35d66ed866b2c3eb418426e6382b0")
      end
    end
  end
end
