summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/chef/provider/git.rb33
-rw-r--r--spec/data/git_bundles/example-repo.gitbundlebin0 -> 1214 bytes
-rw-r--r--spec/functional/resource/git_spec.rb229
-rw-r--r--spec/unit/provider/git_spec.rb20
4 files changed, 273 insertions, 9 deletions
diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb
index cc524a2fcd..fcc7c81a99 100644
--- a/lib/chef/provider/git.rb
+++ b/lib/chef/provider/git.rb
@@ -218,12 +218,37 @@ class Chef
def remote_resolve_reference
Chef::Log.debug("#{@new_resource} resolving remote reference")
- command = git('ls-remote', @new_resource.repository, @new_resource.revision)
+ # The sha pointed to by an annotated tag is identified by the
+ # '^{}' suffix appended to the tag. In order to resolve
+ # annotated tags, we have to search for "revision*" and
+ # post-process. Special handling for 'HEAD' to ignore a tag
+ # named 'HEAD'.
+ rev_pattern = case @new_resource.revision
+ when '', 'HEAD'
+ 'HEAD'
+ else
+ @new_resource.revision + '*'
+ end
+ command = git('ls-remote', @new_resource.repository, rev_pattern)
@resolved_reference = shell_out!(command, run_options).stdout
- if @resolved_reference =~ /^([0-9a-f]{40})\s+(\S+)/
- $1
+ ref_lines = @resolved_reference.split("\n")
+ refs = ref_lines.map { |line| line.split("\t") }
+ # first try for ^{} indicating the commit pointed to by an
+ # annotated tag
+ tagged_commit = refs.find { |m| m[1].end_with?("#{@new_resource.revision}^{}") }
+ # It is possible for a user to create a tag named 'HEAD'.
+ # Using such a degenerate annotated tag would be very
+ # confusing. We avoid the issue by disallowing the use of
+ # annotated tags named 'HEAD'.
+ if tagged_commit && rev_pattern != 'HEAD'
+ tagged_commit[0]
else
- nil
+ found = refs.find { |m| m[1].end_with?(@new_resource.revision) }
+ if found
+ found[0]
+ else
+ nil
+ end
end
end
diff --git a/spec/data/git_bundles/example-repo.gitbundle b/spec/data/git_bundles/example-repo.gitbundle
new file mode 100644
index 0000000000..de08296dc3
--- /dev/null
+++ b/spec/data/git_bundles/example-repo.gitbundle
Binary files differ
diff --git a/spec/functional/resource/git_spec.rb b/spec/functional/resource/git_spec.rb
new file mode 100644
index 0000000000..d4578341cd
--- /dev/null
+++ b/spec/functional/resource/git_spec.rb
@@ -0,0 +1,229 @@
+#
+# Author:: Seth Falcon (<seth@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/shell_out'
+require 'tmpdir'
+
+# Deploy relies heavily on symlinks, so it doesn't work on windows.
+describe Chef::Resource::Git do
+ include Chef::Mixin::ShellOut
+ let(:file_cache_path) { Dir.mktmpdir }
+ let(:deploy_directory) { Dir.mktmpdir }
+
+ let(:node) do
+ Chef::Node.new.tap do |n|
+ n.name "rspec-test"
+ n.consume_external_attrs(@ohai.data, {})
+ end
+ end
+
+ let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) }
+
+ # These tests use git's bundle feature, which is a way to export an entire
+ # git repo (or subset of commits) as a single file.
+ #
+ # Generally you can treat a git bundle as a regular git remote.
+ #
+ # See also: http://git-scm.com/2010/03/10/bundles.html
+ #
+ # Beware that git bundles don't behave exactly the same as real
+ # remotes. To get closer to real remotes, we'll create a local clone
+ # of the bundle to use as a remote for the tests. This at least
+ # gives the expected responses for ls-remote using git version
+ # 1.7.12.4
+ let(:git_bundle_repo) { File.expand_path("git_bundles/example-repo.gitbundle", CHEF_SPEC_DATA) }
+ let(:origin_repo_dir) { Dir.mktmpdir }
+ let(:origin_repo) { "#{origin_repo_dir}/example" }
+
+ # This is the fourth version
+ let(:v1_commit) { "bc5ec79931ae74089aeadca6edc173527613e6d9" }
+ let(:v1_tag) { "9b73fb5e316bfaff7b822b0ccb3e1e08f9885085" }
+ let(:rev_foo) { "ed181b3419b6f489bedab282348162a110d6d3a1" }
+ let(:rev_testing) { "972d153654503bccec29f630c5dd369854a561e8" }
+ let(:rev_head) { "d294fbfd05aa7709ad9a9b8ef6343b17d355bf5f"}
+
+ before(:each) do
+ @old_file_cache_path = Chef::Config[:file_cache_path]
+ shell_out!("git clone #{git_bundle_repo} example", :cwd => origin_repo_dir)
+ Chef::Config[:file_cache_path] = file_cache_path
+ end
+
+ after(:each) do
+ Chef::Config[:file_cache_path] = @old_file_cache_path
+ FileUtils.remove_entry_secure deploy_directory if File.exist?(deploy_directory)
+ FileUtils.remove_entry_secure file_cache_path
+ end
+
+ after(:all) do
+ FileUtils.remove_entry_secure origin_repo_dir
+ end
+
+ before(:all) do
+ @ohai = Ohai::System.new
+ @ohai.require_plugin("os")
+ end
+
+ context "when deploying from an annotated tag" do
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ r.revision "v1.0.0"
+ end
+ end
+
+ # We create a copy of the basic_git_resource so that we can run
+ # the resource again and verify that it doesn't update.
+ let(:copy_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ r.revision "v1.0.0"
+ end
+ end
+
+ it "checks out the revision pointed to by the tag commit, not the tag commit itself" do
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == v1_commit
+ # also verify the tag commit itself is what we expect as an extra sanity check
+ rev = shell_out!('git rev-parse v1.0.0', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ rev.should == v1_tag
+ end
+
+ it "doesn't update if up-to-date" do
+ # this used to fail because we didn't resolve the annotated tag
+ # properly to the pointed to commit.
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == v1_commit
+
+ copy_git_resource.run_action(:sync)
+ copy_git_resource.should_not be_updated
+ end
+ end
+
+ context "when deploying from a SHA revision" do
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository git_bundle_repo
+ end
+ end
+
+ # We create a copy of the basic_git_resource so that we can run
+ # the resource again and verify that it doesn't update.
+ let(:copy_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ end
+ end
+
+ it "checks out the expected revision ed18" do
+ basic_git_resource.revision rev_foo
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_foo
+ end
+
+ it "doesn't update if up-to-date" do
+ basic_git_resource.revision rev_foo
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_foo
+
+ copy_git_resource.revision rev_foo
+ copy_git_resource.run_action(:sync)
+ copy_git_resource.should_not be_updated
+ end
+
+ it "checks out the expected revision 972d" do
+ basic_git_resource.revision rev_testing
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_testing
+ end
+ end
+
+ context "when deploying from a revision named 'HEAD'" do
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ r.revision 'HEAD'
+ end
+ end
+
+ it "checks out the expected revision" do
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_head
+ end
+ end
+
+ context "when deploying from the default revision" do
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ # use default
+ end
+ end
+
+ it "checks out HEAD as the default revision" do
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_head
+ end
+ end
+
+ context "when dealing with a repo with a degenerate tag named 'HEAD'" do
+ before do
+ shell_out!("git tag -m\"degenerate tag\" HEAD ed181b3419b6f489bedab282348162a110d6d3a1",
+ :cwd => origin_repo)
+ end
+
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ r.revision 'HEAD'
+ end
+ end
+
+ let(:git_resource_default_rev) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ # use default of revision
+ end
+ end
+
+ it "checks out the (master) HEAD revision and ignores the tag" do
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD',
+ :cwd => deploy_directory,
+ :returns => [0]).stdout.strip
+ head_rev.should == rev_head
+ end
+
+ it "checks out the (master) HEAD revision when no revision is specified (ignores tag)" do
+ git_resource_default_rev.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD',
+ :cwd => deploy_directory,
+ :returns => [0]).stdout.strip
+ head_rev.should == rev_head
+ end
+
+ end
+end
diff --git a/spec/unit/provider/git_spec.rb b/spec/unit/provider/git_spec.rb
index 275b2907ec..c700aada22 100644
--- a/spec/unit/provider/git_spec.rb
+++ b/spec/unit/provider/git_spec.rb
@@ -91,11 +91,21 @@ describe Chef::Provider::Git do
it "converts resource.revision from a tag to a SHA" do
@resource.revision "v1.0"
- @stdout = "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n"
- @provider.should_receive(:shell_out!).with(@git_ls_remote + "v1.0", {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
+ @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" +
+ "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n")
+ @provider.should_receive(:shell_out!).with(@git_ls_remote + "v1.0*", {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
@provider.target_revision.should eql("503c22a5e41f5ae3193460cca044ed1435029f53")
end
+ it "converts resource.revision from an annotated tag to the tagged SHA (not SHA of tag)" do
+ @resource.revision "v1.0"
+ @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" +
+ "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n" +
+ "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0^{}\n")
+ @provider.should_receive(:shell_out!).with(@git_ls_remote + "v1.0*", {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
+ @provider.target_revision.should eql("663c22a5e41f5ae3193460cca044ed1435029f53")
+ end
+
it "raises an invalid remote reference error if you try to deploy from ``origin'' and assertions are run" do
@resource.revision "origin/"
@provider.action = :checkout
@@ -119,9 +129,9 @@ describe Chef::Provider::Git do
end
it "does not raise an error when the revision is valid and assertions are run." do
- @resource.revision "v1.0"
+ @resource.revision "0.8-alpha"
@stdout = "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n"
- @provider.should_receive(:shell_out!).with(@git_ls_remote + "v1.0", {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
+ @provider.should_receive(:shell_out!).with(@git_ls_remote + "0.8-alpha*", {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
@provider.action = :checkout
::File.stub!(:directory?).with("/my/deploy").and_return(true)
@provider.define_resource_requirements
@@ -146,7 +156,7 @@ b7d19519a1c15f1c1a324e2683bd728b6198ce5a\trefs/tags/0.7.8^{}
ebc1b392fe7e8f0fbabc305c299b4d365d2b4d9b\trefs/tags/chef-server-package
SHAS
@resource.revision ''
- @provider.should_receive(:shell_out!).with(@git_ls_remote, {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
+ @provider.should_receive(:shell_out!).with(@git_ls_remote + "HEAD", {:log_tag=>"git[web2.0 app]", :log_level=>:debug}).and_return(mock("ShellOut result", :stdout => @stdout))
@provider.target_revision.should eql("28af684d8460ba4793eda3e7ac238c864a5d029a")
end
end